| /* Copyright 2024 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* |
| * PD Controller subsystem |
| */ |
| |
| #include "zephyr/device.h" |
| #include "zephyr/toolchain.h" |
| #define DT_DRV_COMPAT named_usbc_port |
| |
| #include "battery.h" |
| #include "battery_smart.h" |
| #include "charge_manager.h" |
| #include "chipset.h" |
| #include "drivers/ucsi_v3.h" |
| #include "ec_commands.h" |
| #include "hooks.h" |
| #include "power_button.h" |
| #include "test/util.h" |
| #include "timer.h" |
| #include "usb_common.h" |
| #include "usb_mux.h" |
| #include "usb_pd.h" |
| #include "usbc/pdc_dpm.h" |
| #include "usbc/pdc_power_mgmt.h" |
| |
| #include <string.h> |
| |
| #include <zephyr/devicetree.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/smf.h> |
| #include <zephyr/sys/atomic.h> |
| #include <zephyr/sys/clock.h> |
| |
| #ifdef CONFIG_ZTEST |
| #include <zephyr/ztest.h> |
| #endif |
| |
| #include <drivers/pdc.h> |
| #include <usbc/utils.h> |
| |
| LOG_MODULE_REGISTER(pdc_power_mgmt, CONFIG_USB_PDC_LOG_LEVEL); |
| |
| #ifdef CONFIG_TEST_SNIFF_POWER_MGMT_PDC_APIS |
| /* Faking PDC APIs directly causes compilation errors of the function being |
| * redefined. For testing only create a wrapper function that can be faked. |
| * These are used to sniff function arguments like in dead battery testing |
| * we are able to FAKE these functions in the test to check the arguments to |
| * validate we are not changing the RDO when more than one sink path is enabled |
| * as its happening during driver initialization. |
| * As these are intended to be FAKED in a test, we won't be able to get coverage |
| * on these functions. |
| */ |
| /* LCOV_EXCL_START */ |
| test_mockable_static_inline int |
| sniff_pdc_set_sink_path(const struct device *dev, bool en) |
| { |
| return pdc_set_sink_path(dev, en); |
| } |
| #define pdc_set_sink_path sniff_pdc_set_sink_path |
| |
| test_mockable_static_inline int sniff_pdc_set_rdo(const struct device *dev, |
| uint32_t rdo) |
| { |
| return pdc_set_rdo(dev, rdo); |
| } |
| #define pdc_set_rdo sniff_pdc_set_rdo |
| /* LCOV_EXCL_STOP */ |
| #endif /* CONFIG_ZTEST */ |
| |
| /** |
| * @brief Event triggered by sending an internal command |
| */ |
| #define PDC_SM_EVENT BIT(0) |
| |
| /** |
| * @brief Event triggered when a public command has completed |
| */ |
| #define PDC_PUBLIC_CMD_COMPLETE_EVENT BIT(1) |
| |
| /** |
| * @brief Event triggered when pdc state has settled |
| */ |
| #define PDC_SM_SETTLED_EVENT BIT(2) |
| |
| /** |
| * @brief Event triggered when get_connector_status from pdc has completed |
| */ |
| #define PDC_PPM_CONNECTOR_STATUS_READY BIT(3) |
| |
| /** |
| * @brief Time delay before running the state machine loop |
| */ |
| #define LOOP_DELAY_MS 25 |
| |
| /** |
| * @brief Time delay to wait for a public command to complete |
| */ |
| #define PUBLIC_CMD_DELAY_MS 10 |
| |
| /** |
| * @brief Maximum time to wait for a command to complete. |
| */ |
| #define PDC_CMD_TIMEOUT_MS 2000 |
| |
| /** |
| * @brief Maximum time to wait for a contract to be established after sending |
| * the SET_RDO command when entering the sink state. |
| * |
| * This value is set empirically based on a typical settling time (500-600ms) |
| * with a generous amount of extra time in case the PDC is slow to report the |
| * new RDO. Not knowing the true RDO could lead to incorrectly seeding |
| * charge_manager, so allow a lot of leeway. Most cases will pass through this |
| * state quickly. After this timeout, assume the RDO is not changing (for any |
| * reason, but likely external factors) and seed charge_manager based on the |
| * currently reported RDO, not what we attempted to set. |
| */ |
| #define NEW_CONTRACT_TIMEOUT K_MSEC(3000) |
| |
| /** |
| * @brief Time to wait for typec only devices (Non PD) to settle |
| */ |
| #define TYPEC_ONLY_SINK_DEBOUNCE_TIME_US (1000 * USEC_PER_MSEC) |
| |
| /** |
| * @brief maximum number of times to try and send a command, or wait for a |
| * public API command to execute (Time is 2s) |
| * |
| */ |
| #define WAIT_MAX (PDC_CMD_TIMEOUT_MS / LOOP_DELAY_MS) |
| |
| /** |
| * @brief Maximum time to wait for PDC state to settle. |
| */ |
| #define PDC_SM_SETTLED_TIMEOUT_MS \ |
| CONFIG_PDC_POWER_MGMT_STATE_MACHINE_SETTLED_TIMEOUT_MS |
| |
| /** @brief Delay to wait for stable power state before running hooks */ |
| #define PDC_POWER_STATE_DEBOUNCE_MS \ |
| (K_MSEC(CONFIG_PDC_POWER_MGMT_POWER_STATE_DEBOUNCE_PERIOD_MS)) |
| |
| /** |
| * @brief maximum number of times to try and send a command, or wait for a |
| * public API command to execute (Time is 2s) |
| * |
| */ |
| #define CMD_RESEND_MAX 2 |
| |
| /** |
| * @brief maximum number of VDOs |
| */ |
| #define VDO_NUM 8 |
| |
| /** |
| * @brief Minimum long button press in seconds. |
| */ |
| #define PD_POWER_BUTTON_LONG_PRESS 4 |
| |
| /** |
| * @brief Button press timeout in seconds. |
| */ |
| #define PD_POWER_BUTTON_PRESS_TIMEOUT 8 |
| |
| /** |
| * @brief PDC driver commands |
| */ |
| enum pdc_cmd_t { |
| /** CMD_PDC_NONE */ |
| CMD_PDC_NONE, |
| /** CMD_PDC_RESET */ |
| CMD_PDC_RESET, |
| /** CMD_PDC_SET_POWER_LEVEL */ |
| CMD_PDC_SET_POWER_LEVEL, |
| /** CMD_PDC_SET_CCOM */ |
| CMD_PDC_SET_CCOM, |
| /** CMD_PDC_SET_DRP */ |
| CMD_PDC_SET_DRP, |
| /** CMD_PDC_GET_DRP */ |
| CMD_PDC_GET_DRP, |
| /** CMD_PDC_GET_PDOS */ |
| CMD_PDC_GET_PDOS, |
| /** CMD_PDC_GET_RDO */ |
| CMD_PDC_GET_RDO, |
| /** CMD_PDC_SET_RDO */ |
| CMD_PDC_SET_RDO, |
| /** CMD_PDC_SET_SINK_PATH */ |
| CMD_PDC_SET_SINK_PATH, |
| /** CMD_PDC_READ_POWER_LEVEL */ |
| CMD_PDC_READ_POWER_LEVEL, |
| /** CMD_PDC_GET_INFO */ |
| CMD_PDC_GET_INFO, |
| /** CMD_PDC_GET_CONNECTOR_CAPABILITY */ |
| CMD_PDC_GET_CONNECTOR_CAPABILITY, |
| /** CMD_PDC_SET_UOR */ |
| CMD_PDC_SET_UOR, |
| /** CMD_PDC_SET_PDR */ |
| CMD_PDC_SET_PDR, |
| /** CMD_PDC_GET_CONNECTOR_STATUS */ |
| CMD_PDC_GET_CONNECTOR_STATUS, |
| /** CMD_PDC_GET_CABLE_PROPERTY */ |
| CMD_PDC_GET_CABLE_PROPERTY, |
| /** CMD_PDC_GET_VDO */ |
| CMD_PDC_GET_VDO, |
| /** CMD_PDC_CONNECTOR_RESET */ |
| CMD_PDC_CONNECTOR_RESET, |
| /** CMD_PDC_GET_IDENTITY_DISCOVERY */ |
| CMD_PDC_GET_IDENTITY_DISCOVERY, |
| /** CMD_PDC_IS_SOURCING_VCONN */ |
| CMD_PDC_IS_VCONN_SOURCING, |
| /** CMD_PDC_GET_PD_VDO_DP_CFG */ |
| CMD_PDC_GET_PD_VDO_DP_CFG_SELF, |
| /** CMD_PDC_SET_PDOS */ |
| CMD_PDC_SET_PDOS, |
| /** CMD_PDC_GET_PCH_DATA_STATUS */ |
| CMD_PDC_GET_PCH_DATA_STATUS, |
| /** CMD_PDC_ACK_CC_CI */ |
| CMD_PDC_ACK_CC_CI, |
| /** CMD_PDC_GET_LPM_PPM_INFO */ |
| CMD_PDC_GET_LPM_PPM_INFO, |
| /** CMD_PDC_SET_FRS */ |
| CMD_PDC_SET_FRS, |
| /** CMD_PDC_GET_ATTENTION_VDO */ |
| CMD_PDC_GET_ATTENTION_VDO, |
| /** CMD_PDC_GET_SBU_MUX_MODE */ |
| CMD_PDC_GET_SBU_MUX_MODE, |
| /** CMD_PDC_SET_SBU_MUX_MODE */ |
| CMD_PDC_SET_SBU_MUX_MODE, |
| /** CMD_PDC_SET_AP_POWER_STATE */ |
| CMD_PDC_SET_AP_POWER_STATE, |
| /** CMD_PDC_SET_BBR_CTS */ |
| CMD_PDC_SET_BBR_CTS, |
| /** CMD_PDC_SET_BATTERY_STATUS */ |
| CMD_PDC_SET_BATTERY_STATUS, |
| /** CMD_PDC_SET_BATTERY_CAPABILITY*/ |
| CMD_PDC_SET_BATTERY_CAPABILITY, |
| /** CMD_PDC_GET_VENDOR_STATUS */ |
| CMD_PDC_GET_VENDOR_STATUS, |
| /** CMD_PDC_GET_ALERT */ |
| CMD_PDC_GET_ALERT, |
| /** CMD_PDC_SET_MAX_PDP */ |
| CMD_PDC_SET_MAX_PDP, |
| /** CMD_PDC_COUNT */ |
| CMD_PDC_COUNT |
| }; |
| |
| /** |
| * @brief Send Local States |
| */ |
| enum send_cmd_state_t { |
| /** SEND_CMD_START_ENTRY */ |
| SEND_CMD_START_ENTRY, |
| /** SEND_CMD_START_RUN */ |
| SEND_CMD_START_RUN, |
| /** SEND_CMD_WAIT_ENTRY */ |
| SEND_CMD_WAIT_ENTRY, |
| /** SEND_CMD_WAIT_RUN */ |
| SEND_CMD_WAIT_RUN, |
| /** SEND_CMD_WAIT_EXIT */ |
| SEND_CMD_WAIT_EXIT, |
| }; |
| |
| /** |
| * @brief Command type |
| */ |
| struct cmd_t { |
| /** Command to send */ |
| enum pdc_cmd_t cmd; |
| /** True if command is pending */ |
| bool pending; |
| /** != 0 if command failed to send */ |
| int8_t error; |
| }; |
| |
| /** |
| * @brief Send command type |
| */ |
| struct send_cmd_t { |
| /** Send command local state */ |
| enum send_cmd_state_t local_state; |
| /* Wait counter used in local wait state */ |
| uint16_t wait_counter; |
| /* Command resend counter */ |
| uint8_t resend_counter; |
| /* Command sent from public API */ |
| struct cmd_t public; |
| /* Command sent from internal API */ |
| struct cmd_t intern; |
| }; |
| |
| /** |
| * @brief SNK Attached Local States |
| */ |
| enum snk_attached_local_state_t { |
| /** SNK_ATTACHED_GET_CONNECTOR_CAPABILITY */ |
| SNK_ATTACHED_GET_CONNECTOR_CAPABILITY, |
| /** SNK_ATTACHED_SET_DR_SWAP_POLICY */ |
| SNK_ATTACHED_SET_DR_SWAP_POLICY, |
| /** SNK_ATTACHED_SET_PR_SWAP_POLICY */ |
| SNK_ATTACHED_SET_PR_SWAP_POLICY, |
| /** SNK_ATTACHED_GET_PDOS */ |
| SNK_ATTACHED_GET_PDOS, |
| /** SNK_ATTACHED_GET_VDO */ |
| SNK_ATTACHED_GET_VDO, |
| /** SNK_ATTACHED_WAIT_FOR_CONTRACT */ |
| SNK_ATTACHED_WAIT_FOR_CONTRACT, |
| /** SNK_ATTACHED_SYNC_CHARGE_MGR */ |
| SNK_ATTACHED_SYNC_CHARGE_MGR, |
| /** SNK_ATTACHED_SET_SINK_PATH */ |
| SNK_ATTACHED_SET_SINK_PATH, |
| /** SNK_ATTACHED_EVALUATE_PDOS */ |
| SNK_ATTACHED_EVALUATE_PDOS, |
| /** SNK_ATTACHED_GET_SINK_PDO */ |
| SNK_ATTACHED_GET_SINK_PDO, |
| /** SNK_ATTACHED_GET_CABLE_PROPERTY */ |
| SNK_ATTACHED_GET_CABLE_PROPERTY, |
| /** SNK_ATTACHED_READ_POWER_LEVEL */ |
| SNK_ATTACHED_READ_POWER_LEVEL, |
| /** SNK_ATTACHED_RUN */ |
| SNK_ATTACHED_RUN, |
| /* Mark end of enum */ |
| SNK_ATTACHED_INVALID, |
| }; |
| |
| #ifdef CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES |
| /* Names of the Sink-attached substates. */ |
| const static char *snk_attached_local_state_names[] = { |
| [SNK_ATTACHED_GET_CONNECTOR_CAPABILITY] = "GET_CONN_CAP", |
| [SNK_ATTACHED_SET_DR_SWAP_POLICY] = "SET_DR_SWAP_POLICY", |
| [SNK_ATTACHED_SET_PR_SWAP_POLICY] = "SET_PR_SWAP_POLICY", |
| [SNK_ATTACHED_GET_PDOS] = "GET_PDOS", |
| [SNK_ATTACHED_GET_VDO] = "GET_VDO", |
| [SNK_ATTACHED_WAIT_FOR_CONTRACT] = "WAIT_FOR_CNRCT", |
| [SNK_ATTACHED_SYNC_CHARGE_MGR] = "SYNC_CHARGE_MGR", |
| [SNK_ATTACHED_SET_SINK_PATH] = "SET_SINK_PATH", |
| [SNK_ATTACHED_EVALUATE_PDOS] = "EVAL_PDOS", |
| [SNK_ATTACHED_GET_SINK_PDO] = "GET_SINK_PDO", |
| [SNK_ATTACHED_GET_CABLE_PROPERTY] = "GET_CABLE_PROP", |
| [SNK_ATTACHED_READ_POWER_LEVEL] = "RD_PWR_LVL", |
| [SNK_ATTACHED_RUN] = "RUN", |
| }; |
| |
| BUILD_ASSERT(ARRAY_SIZE(snk_attached_local_state_names) == SNK_ATTACHED_INVALID, |
| "Please update substate names array"); |
| #endif /* CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES */ |
| |
| /** |
| * @brief SRC Attached Local States |
| */ |
| enum src_attached_local_state_t { |
| /** SRC_ATTACHED_SET_SINK_PATH_OFF */ |
| SRC_ATTACHED_SET_SINK_PATH_OFF, |
| /** SRC_ATTACHED_GET_CONNECTOR_CAPABILITY */ |
| SRC_ATTACHED_GET_CONNECTOR_CAPABILITY, |
| /** SRC_ATTACHED_SET_DR_SWAP_POLICY */ |
| SRC_ATTACHED_SET_DR_SWAP_POLICY, |
| /** SRC_ATTACHED_SET_PR_SWAP_POLICY */ |
| SRC_ATTACHED_SET_PR_SWAP_POLICY, |
| /** SRC_ATTACHED_READ_POWER_LEVEL */ |
| SRC_ATTACHED_READ_POWER_LEVEL, |
| /** SRC_ATTACHED_GET_VDO */ |
| SRC_ATTACHED_GET_VDO, |
| /** SRC_ATTACHED_GET_CABLE_PROPERTY */ |
| SRC_ATTACHED_GET_CABLE_PROPERTY, |
| /** SRC_ATTACHED_RUN */ |
| SRC_ATTACHED_RUN, |
| /* Mark end of enum */ |
| SRC_ATTACHED_INVALID, |
| }; |
| |
| #ifdef CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES |
| /* Names of the Source-attached substates. */ |
| const static char *src_attached_local_state_names[] = { |
| [SRC_ATTACHED_SET_SINK_PATH_OFF] = "SET_SINK_PATH_OFF", |
| [SRC_ATTACHED_GET_CONNECTOR_CAPABILITY] = "GET_CONN_CAP", |
| [SRC_ATTACHED_SET_DR_SWAP_POLICY] = "SET_DR_SWAP_POLICY", |
| [SRC_ATTACHED_SET_PR_SWAP_POLICY] = "SET_PR_SWAP_POLICY", |
| [SRC_ATTACHED_READ_POWER_LEVEL] = "RD_PWR_LVL", |
| [SRC_ATTACHED_GET_VDO] = "GET_VDO", |
| [SRC_ATTACHED_GET_CABLE_PROPERTY] = "GET_CABLE_PROP", |
| [SRC_ATTACHED_RUN] = "RUN", |
| }; |
| |
| BUILD_ASSERT(ARRAY_SIZE(src_attached_local_state_names) == SRC_ATTACHED_INVALID, |
| "Please update substate names array"); |
| #endif /* CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES */ |
| |
| /** |
| * @brief TypeC SNK Attached Local States |
| */ |
| enum snk_typec_attached_local_state_t { |
| /** SNK_TYPEC_ATTACHED_SET_CHARGE_CURRENT */ |
| SNK_TYPEC_ATTACHED_SET_CHARGE_CURRENT, |
| /** SNK_TYPEC_ATTACHED_DEBOUNCE */ |
| SNK_TYPEC_ATTACHED_DEBOUNCE, |
| /** SNK_TYPEC_READ_POWER_LEVEL */ |
| SNK_TYPEC_READ_POWER_LEVEL, |
| /** SNK_TYPEC_ATTACHED_RUN */ |
| SNK_TYPEC_ATTACHED_RUN, |
| /* Mark end of enum */ |
| SNK_TYPEC_ATTACHED_INVALID, |
| }; |
| |
| #ifdef CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES |
| /* Names of the Type-C only Sink-attached substates. */ |
| const static char *snk_typec_attached_local_state_names[] = { |
| [SNK_TYPEC_ATTACHED_SET_CHARGE_CURRENT] = "SET_CHRG_CUR", |
| [SNK_TYPEC_ATTACHED_DEBOUNCE] = "DEBOUNCE", |
| [SNK_TYPEC_READ_POWER_LEVEL] = "RD_PWR_LVL", |
| [SNK_TYPEC_ATTACHED_RUN] = "RUN", |
| |
| }; |
| |
| BUILD_ASSERT(ARRAY_SIZE(snk_typec_attached_local_state_names) == |
| SNK_TYPEC_ATTACHED_INVALID, |
| "Please update substate names array"); |
| #endif /* CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES */ |
| |
| /** |
| * @brief TypeC SRC Attached Local States |
| */ |
| enum src_typec_attached_local_state_t { |
| /** SRC_TYPEC_ATTACHED_SET_SINK_PATH_OFF */ |
| SRC_TYPEC_ATTACHED_SET_SINK_PATH_OFF, |
| /** SRC_TYPEC_ATTACHED_DEBOUNCE */ |
| SRC_TYPEC_ATTACHED_DEBOUNCE, |
| /** SRC_TYPEC_ATTACHED_ADD_SINK */ |
| SRC_TYPEC_ATTACHED_ADD_SINK, |
| /** SRC_TYPEC_READ_POWER_LEVEL */ |
| SRC_TYPEC_READ_POWER_LEVEL, |
| /** SRC_TYPEC_ATTACHED_RUN */ |
| SRC_TYPEC_ATTACHED_RUN, |
| /* Mark end of enum */ |
| SRC_TYPEC_ATTACHED_INVALID, |
| }; |
| |
| #ifdef CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES |
| /* Names of the Type-C only Source-attached substates. */ |
| const static char *src_typec_attached_local_state_names[] = { |
| [SRC_TYPEC_ATTACHED_SET_SINK_PATH_OFF] = "SET_SINK_PATH_OFF", |
| [SRC_TYPEC_ATTACHED_DEBOUNCE] = "DEBOUNCE", |
| [SRC_TYPEC_ATTACHED_ADD_SINK] = "ADD_SINK", |
| [SRC_TYPEC_READ_POWER_LEVEL] = "RD_PWR_LVL", |
| [SRC_TYPEC_ATTACHED_RUN] = "RUN", |
| }; |
| |
| BUILD_ASSERT(ARRAY_SIZE(src_typec_attached_local_state_names) == |
| SRC_TYPEC_ATTACHED_INVALID, |
| "Please update substate names array"); |
| #endif /* CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES */ |
| |
| /** |
| * @brief Unattached Local States |
| */ |
| enum unattached_local_state_t { |
| /** UNATTACHED_SET_SINK_PATH_OFF */ |
| UNATTACHED_SET_SINK_PATH_OFF, |
| /** UNATTACHED_RUN */ |
| UNATTACHED_RUN, |
| /* Mark end of enum */ |
| UNATTACHED_INVALID, |
| }; |
| |
| #ifdef CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES |
| /* Names of the Source-attached substates. */ |
| const static char *unattached_local_state_names[] = { |
| [UNATTACHED_SET_SINK_PATH_OFF] = "SET_SINK_PATH_OFF", |
| [UNATTACHED_RUN] = "RUN", |
| }; |
| |
| BUILD_ASSERT(ARRAY_SIZE(unattached_local_state_names) == UNATTACHED_INVALID, |
| "Please update substate names array"); |
| #endif /* CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES */ |
| |
| /** |
| * @brief Initialization local states. Carry out these steps upon subsystem |
| * startup. INIT_GET_CONNECTOR_STATUS must be the final step, as this |
| * calls the GET_CONNECTOR_STATUS PDC command and will directly |
| * transition to the appropriate unattached or attached state. |
| */ |
| enum init_local_state_t { |
| /** INIT_WAIT_FOR_READY - Wait until the underlying PDC driver is |
| * initialized. |
| */ |
| INIT_WAIT_FOR_READY, |
| /** INIT_SET_SBU_MUX_FORCED_DEBUG - set the port's SBU mux to |
| * forced-debug if the COMMON_POLICY_SET_SBU_MUX_TO_FORCED_DEBUG flag |
| * is set. |
| */ |
| INIT_SET_SBU_MUX_FORCED_DEBUG, |
| /** INIT_SET_SINK_PDOS - Set the sink PDOs (sink capabilities) on the |
| * PDC based on product configuration data. |
| */ |
| INIT_SET_SINK_PDOS, |
| /** INIT_SET_SRC_PDOS - pdc sets src pdo in advance during |
| * initialization. |
| */ |
| INIT_SET_SRC_PDOS, |
| /** INIT_FRS - Enable or disable FRS on the device during |
| * initialization. |
| */ |
| INIT_SET_FRS, |
| /** |
| * INIT_SET_MAX_PDP - Set the device's max PDP during init based on |
| * number of 3A ports. |
| */ |
| INIT_SET_MAX_PDP, |
| /** INIT_GET_CONNECTOR_STATUS - Get current status. This state does not |
| * return; the state machine will transition to the unattached or one |
| * of the attached run states after handling the response. |
| */ |
| INIT_GET_CONNECTOR_STATUS, |
| /* Mark end of enum */ |
| INIT_INVALID, |
| }; |
| |
| #ifdef CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES |
| /* Names of the init substates. */ |
| const static char *init_local_state_names[] = { |
| [INIT_WAIT_FOR_READY] = "WAIT_FOR_READY", |
| [INIT_SET_SINK_PDOS] = "SET_SINK_PDOS", |
| [INIT_SET_SRC_PDOS] = "SET_SRC_PDOS", |
| [INIT_SET_FRS] = "SET_FRS", |
| [INIT_SET_MAX_PDP] = "SET_MAX_PDP", |
| [INIT_GET_CONNECTOR_STATUS] = "GET_CONN_STATUS", |
| }; |
| |
| BUILD_ASSERT(ARRAY_SIZE(init_local_state_names) == INIT_INVALID, |
| "Please update substate names array"); |
| #endif /* CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES */ |
| |
| /** |
| * @brief CCI Event Flags |
| */ |
| enum cci_flag_t { |
| /** CCI_BUSY */ |
| CCI_BUSY, |
| /** CCI_ERROR */ |
| CCI_ERROR, |
| /** CCI_CMD_COMPLETED */ |
| CCI_CMD_COMPLETED, |
| /** CCI_EVENT: Used to trigger querying connector status */ |
| CCI_EVENT, |
| /** Used to query vendor defined connector change bits */ |
| CCI_VENDOR_EVENT, |
| /** CCI_CAM_CHANGE */ |
| CCI_CAM_CHANGE, |
| /** CCI_ACK */ |
| CCI_ACK, |
| /** CCI_ATTENTION */ |
| CCI_ATTENTION, |
| /** CCI_PPM_EVENT */ |
| CCI_PPM_EVENT, |
| /** |
| * Trigger sending new SINK PDOs for the LPM. This is not part of |
| * the UCSI connector change flags, but this action is valid |
| * during UNATTACHED, SNK_ATTACHED, SRC_ATTACHED, SNK_TYPEC_ONLY, |
| * and SRC_TYPEC_ONLY |
| */ |
| CCI_SET_SINK_PDOS, |
| /** CCI_FLAGS_COUNT */ |
| CCI_FLAGS_COUNT |
| }; |
| |
| /** |
| * @brief PDC Command Names |
| */ |
| test_export_static const char *const pdc_cmd_names[] = { |
| [CMD_PDC_NONE] = "", |
| [CMD_PDC_RESET] = "PDC_RESET", |
| [CMD_PDC_SET_POWER_LEVEL] = "PDC_SET_POWER_LEVEL", |
| [CMD_PDC_SET_CCOM] = "PDC_SET_CCOM", |
| [CMD_PDC_SET_DRP] = "PDC_SET_DRP", |
| [CMD_PDC_GET_DRP] = "PDC_GET_DRP", |
| [CMD_PDC_GET_PDOS] = "PDC_GET_PDOS", |
| [CMD_PDC_GET_RDO] = "PDC_GET_RDO", |
| [CMD_PDC_SET_RDO] = "PDC_SET_RDO", |
| [CMD_PDC_SET_SINK_PATH] = "PDC_SET_SINK_PATH", |
| [CMD_PDC_READ_POWER_LEVEL] = "PDC_READ_POWER_LEVEL", |
| [CMD_PDC_GET_INFO] = "PDC_GET_INFO", |
| [CMD_PDC_GET_CONNECTOR_CAPABILITY] = "PDC_GET_CONNECTOR_CAPABILITY", |
| [CMD_PDC_SET_UOR] = "PDC_SET_UOR", |
| [CMD_PDC_SET_PDR] = "PDC_SET_PDR", |
| [CMD_PDC_GET_CONNECTOR_STATUS] = "PDC_GET_CONNECTOR_STATUS", |
| [CMD_PDC_GET_CABLE_PROPERTY] = "PDC_GET_CABLE_PROPERTY", |
| [CMD_PDC_GET_VDO] = "PDC_GET_VDO", |
| [CMD_PDC_CONNECTOR_RESET] = "PDC_CONNECTOR_RESET", |
| [CMD_PDC_GET_IDENTITY_DISCOVERY] = "PDC_GET_IDENTITY_DISCOVERY", |
| [CMD_PDC_IS_VCONN_SOURCING] = "PDC_IS_VCONN_SOURCING", |
| [CMD_PDC_GET_PD_VDO_DP_CFG_SELF] = "PDC_GET_PD_VDO_DP_CFG_SELF", |
| [CMD_PDC_SET_PDOS] = "PDC_SET_PDOS", |
| [CMD_PDC_GET_PCH_DATA_STATUS] = "PDC_GET_PCH_DATA_STATUS", |
| [CMD_PDC_ACK_CC_CI] = "PDC_ACK_CC_CI", |
| [CMD_PDC_GET_LPM_PPM_INFO] = "PDC_GET_LPM_PPM_INFO", |
| [CMD_PDC_SET_FRS] = "PDC_SET_FRS", |
| [CMD_PDC_GET_ATTENTION_VDO] = "PDC_GET_ATTENTION_VDO", |
| [CMD_PDC_GET_SBU_MUX_MODE] = "PDC_GET_SBU_MUX_MODE", |
| [CMD_PDC_SET_SBU_MUX_MODE] = "PDC_SET_SBU_MUX_MODE", |
| [CMD_PDC_SET_AP_POWER_STATE] = "PDC_SET_AP_POWER_STATE", |
| [CMD_PDC_SET_BBR_CTS] = "PDC_SET_BBR_CTS", |
| [CMD_PDC_SET_BATTERY_STATUS] = "PDC_SET_BATTERY_STATUS", |
| [CMD_PDC_SET_BATTERY_CAPABILITY] = "PDC_SET_BATTERY_CAPABILITY", |
| [CMD_PDC_GET_VENDOR_STATUS] = "PDC_GET_VENDOR_STATUS", |
| [CMD_PDC_GET_ALERT] = "PDC_GET_ALERT", |
| [CMD_PDC_SET_MAX_PDP] = "PDC_SET_MAX_PDP", |
| }; |
| const int pdc_cmd_types = CMD_PDC_COUNT; |
| |
| BUILD_ASSERT(ARRAY_SIZE(pdc_cmd_names) == CMD_PDC_COUNT); |
| |
| /** |
| * @brief State Machine State Names |
| */ |
| static const char *const pdc_state_names[] = { |
| [PDC_INIT] = "PDC Init", |
| [PDC_UNATTACHED] = "Unattached", |
| [PDC_SNK_ATTACHED] = "Attached.SNK", |
| [PDC_SRC_ATTACHED] = "Attached.SRC", |
| [PDC_SEND_CMD_START] = "SendCmdStart", |
| [PDC_SEND_CMD_WAIT] = "SendCmdWait", |
| [PDC_SRC_TYPEC_ONLY] = "TypeCSrcAttached", |
| [PDC_SNK_TYPEC_ONLY] = "TypeCSnkAttached", |
| [PDC_SUSPENDED] = "Suspended", |
| [PDC_DISABLED] = "Disabled", |
| [PDC_INVALID] = "PDC Invalid", |
| }; |
| |
| BUILD_ASSERT(ARRAY_SIZE(pdc_state_names) == PDC_STATE_COUNT, |
| "pdc_state_names array has wrong number of elements"); |
| |
| /** |
| * @brief Common policy flags which are applicable regardless of attached state |
| */ |
| enum policy_common_t { |
| /** COMMON_POLICY_SET_POWER_STATE */ |
| COMMON_POLICY_SET_POWER_STATE, |
| /** COMMON_POLICY_SET_RP */ |
| COMMON_POLICY_SET_RP, |
| /** COMMON_POLICY_GET_ALERT */ |
| COMMON_POLICY_GET_ALERT, |
| /** When set, run CMD_PDC_SET_SBU_MUX_MODE to set the port's SBU mux |
| * mode to force-debug. This should only be set on the port acting |
| * as the CCD port. |
| */ |
| COMMON_POLICY_SET_SBU_MUX_TO_FORCED_DEBUG, |
| /** COMMON_POLICY_COUNT */ |
| COMMON_POLICY_COUNT, |
| }; |
| |
| struct pdc_common_policy_t { |
| /** Common policy flags */ |
| ATOMIC_DEFINE(flags, COMMON_POLICY_COUNT); |
| /** Current AP power state */ |
| enum power_state ap_state; |
| }; |
| |
| /** |
| * @brief Unattached policy flags |
| */ |
| enum policy_unattached_t { |
| /** UNA_POLICY_TCC */ |
| UNA_POLICY_TCC, |
| /** UNA_POLICY_CC_MODE */ |
| UNA_POLICY_CC_MODE, |
| /** UNA_POLICY_DRP_MODE */ |
| UNA_POLICY_DRP_MODE, |
| /** UNA_POLICY_UPDATE_SRC_CAPS */ |
| UNA_POLICY_UPDATE_SRC_CAPS, |
| /** UNA_POLICY_COUNT */ |
| UNA_POLICY_COUNT, |
| }; |
| |
| /** |
| * @brief Unattached policy object |
| */ |
| struct pdc_unattached_policy_t { |
| /** Unattached policy flags */ |
| ATOMIC_DEFINE(flags, UNA_POLICY_COUNT); |
| /** Type-C current */ |
| enum usb_typec_current_t tcc; |
| /** CC Operation Mode */ |
| enum ccom_t cc_mode; |
| /** DRP Mode */ |
| enum drp_mode_t drp_mode; |
| }; |
| |
| /** |
| * @brief Sink policy flags |
| */ |
| enum policy_snk_attached_t { |
| /** Request a new power level */ |
| SNK_POLICY_NEW_POWER_REQUEST, |
| /** New source caps */ |
| SNK_POLICY_NEW_SRC_CAPS_AVAILABLE, |
| /** Enables swap to Source */ |
| SNK_POLICY_SWAP_TO_SRC, |
| /** Selects the low power PDO on connect */ |
| SNK_POLICY_REQUEST_LOW_POWER_PDO, |
| /** Selects the highest powered PDO on connect */ |
| SNK_POLICY_REQUEST_HIGH_POWER_PDO, |
| /** Selects the active charge port */ |
| SNK_POLICY_SET_ACTIVE_CHARGE_PORT, |
| /** Runs a test to determine if we should become a source instead */ |
| SNK_POLICY_EVAL_SWAP_TO_SRC, |
| /** Triggers an update of the allow_pr_swap bit in CMD_SET_DRP */ |
| SNK_POLICY_UPDATE_ALLOW_PR_SWAP, |
| /** Sends SET_PDO to the LPM. */ |
| SNK_POLICY_UPDATE_SRC_CAPS, |
| /** Evaluates sink PDOs from DRP partner. */ |
| SNK_POLICY_EVAL_SNK_FIXED_PDO, |
| /** TypeC sink only */ |
| SNK_POLICY_UPDATE_TYPEC_CURRENT, |
| /** Update battery status */ |
| SNK_POLICY_UPDATE_BATTERY_STATUS, |
| /** Update battery capability */ |
| SNK_POLICY_UPDATE_BATTERY_CAPABILITY, |
| /** SNK_POLICY_COUNT */ |
| SNK_POLICY_COUNT, |
| }; |
| |
| /** |
| * @brief Attached state |
| */ |
| enum attached_state_t { |
| /* INIT_STATE */ |
| INIT_STATE, |
| /** UNATTACHED_STATE */ |
| UNATTACHED_STATE, |
| /** SRC_ATTACHED_STATE */ |
| SRC_ATTACHED_STATE, |
| /** SNK_ATTACHED_STATE */ |
| SNK_ATTACHED_STATE, |
| /** SRC_ATTACHED_TYPEC_ONLY_STATE */ |
| SRC_ATTACHED_TYPEC_ONLY_STATE, |
| /** SNK_ATTACHED_TYPEC_ONLY_STATE */ |
| SNK_ATTACHED_TYPEC_ONLY_STATE, |
| }; |
| |
| static const char *const attached_state_names[] = { |
| [INIT_STATE] = "Init", |
| [UNATTACHED_STATE] = "Unattached", |
| [SRC_ATTACHED_STATE] = "Attached.SRC", |
| [SNK_ATTACHED_STATE] = "Attached.SNK", |
| [SRC_ATTACHED_TYPEC_ONLY_STATE] = "TypeCSrcAttached", |
| [SNK_ATTACHED_TYPEC_ONLY_STATE] = "TypeCSnkAttached", |
| }; |
| |
| /** |
| * @brief Common struct for PDOs |
| */ |
| struct pdc_pdos_t { |
| /** PDOs */ |
| uint32_t pdos[PDO_MAX_OBJECTS]; |
| /** PDO count */ |
| uint8_t pdo_count; |
| }; |
| |
| /** |
| * @brief Struct for SET_PDOS command |
| */ |
| struct set_pdos_t { |
| /** PDOs for SRC or SNK CAPs */ |
| uint32_t pdos[PDO_MAX_OBJECTS]; |
| /** PDO count */ |
| uint8_t count; |
| /** SRC or SNK pdo */ |
| enum pdo_type_t type; |
| }; |
| |
| /** |
| * @brief Sink attached policy object |
| */ |
| struct pdc_snk_attached_policy_t { |
| /** SNK Attached policy flags */ |
| ATOMIC_DEFINE(flags, SNK_POLICY_COUNT); |
| /** Currently active PDO */ |
| uint32_t pdo; |
| /** Current active PDO index */ |
| uint32_t pdo_index; |
| /** PDO count */ |
| uint8_t pdo_count; |
| /** PDOs for Source Caps */ |
| struct pdc_pdos_t partner_src_pdos; |
| /** Sent RDO */ |
| uint32_t rdo; |
| /** New RDO to send */ |
| uint32_t rdo_to_send; |
| /** If true, accept a power role swap request from port partner */ |
| bool accept_power_role_swap; |
| }; |
| |
| /** |
| * @brief Source attached policy flags |
| */ |
| enum policy_src_attached_t { |
| /** Enables swap to Sink */ |
| SRC_POLICY_SWAP_TO_SNK, |
| /** Forces sink-only operation, even if it requires a disconnect */ |
| SRC_POLICY_FORCE_SNK, |
| /** Trigger a call into DPM source current balancing policy */ |
| SRC_POLICY_EVAL_SNK_FIXED_PDO, |
| /** Triggers a Get_Sink_Cap message to the partner. */ |
| SRC_POLICY_GET_SINK_CAPS, |
| /** Set new SRC CAP for PDC port in source power role */ |
| SRC_POLICY_UPDATE_SRC_CAPS, |
| /** |
| * Triggers sending CMD_PDC_GET_RDO to extract RDO for current |
| * balancing policy. |
| */ |
| SRC_POLICY_GET_RDO, |
| /** Get source caps from DRP sink partner */ |
| SRC_POLICY_GET_SRC_CAPS, |
| /** Evaluate source PDOs from DRP sink partner */ |
| SRC_POLICY_EVAL_SRC_PDOS, |
| /** Triggers an update of the allow_pr_swap bit in CMD_SET_DRP */ |
| SRC_POLICY_UPDATE_ALLOW_PR_SWAP, |
| /** Update battery status */ |
| SRC_POLICY_UPDATE_BATTERY_STATUS, |
| /** Update battery capability */ |
| SRC_POLICY_UPDATE_BATTERY_CAPABILITY, |
| /** Hard Reset bus powered device */ |
| SRC_POLICY_TBT_RESET, |
| |
| /** SRC_POLICY_COUNT */ |
| SRC_POLICY_COUNT |
| }; |
| |
| /** |
| * @brief Source attached policy object |
| */ |
| struct pdc_src_attached_policy_t { |
| /** SRC Attached policy flags */ |
| ATOMIC_DEFINE(flags, SRC_POLICY_COUNT); |
| /** PDOs for Sink caps */ |
| struct pdc_pdos_t partner_snk_pdos; |
| /** Request RDO from port partner */ |
| uint32_t rdo; |
| /** Stores our desired LPM source PDO. This is sent to the PDC when the |
| * {UNA|SNK|SRC}_POLICY_UPDATE_SRC_CAPS policy flags are triggered. |
| * PDC Power Management chooses this value based on policy. |
| */ |
| uint32_t lpm_src_pdo; |
| /** If true, accept a power role swap request from port partner */ |
| bool accept_power_role_swap; |
| }; |
| |
| /** |
| * @brief Indices used to map which VDO to use to extract the desired field |
| */ |
| #define IDENTITY_VID_VDO_IDX 0 |
| #define IDENTITY_PTYPE_VDO_IDX 0 |
| #define IDENTITY_PID_VDO_IDX 1 |
| |
| /** |
| * @brief Invalid value for VDO used to check if VDO has been queried already. |
| */ |
| #define INVALID_VDO_VALUE -1u |
| |
| /** |
| * @brief Table of VDO types to request in the GET_VDO command |
| */ |
| static const enum vdo_type_t vdo_discovery_list[] = { |
| VDO_ID_HEADER, |
| VDO_PRODUCT, |
| }; |
| |
| /** |
| * @brief PDC Port object |
| */ |
| struct pdc_port_t { |
| /** State machine context */ |
| struct smf_ctx ctx; |
| /** Subsystem device */ |
| const struct device *dev; |
| /** PDC device */ |
| const struct device *pdc; |
| |
| /** CCI flags */ |
| ATOMIC_DEFINE(cci_flags, CCI_FLAGS_COUNT); |
| /** PDC Cmd flags */ |
| ATOMIC_DEFINE(pdc_cmd_flags, CMD_PDC_COUNT); |
| /** Flag to suspend the PDC Power Mgmt state machine */ |
| atomic_t suspend; |
| /** Flag to notify that a Hard Reset was sent */ |
| atomic_t hard_reset_sent; |
| |
| /** Init local state variable */ |
| enum init_local_state_t init_local_state; |
| /** Source TypeC attached local state variable */ |
| enum src_typec_attached_local_state_t src_typec_attached_local_state; |
| /** Sink TypeC attached local state variable */ |
| enum snk_typec_attached_local_state_t snk_typec_attached_local_state; |
| /** Unattached local state variable */ |
| enum unattached_local_state_t unattached_local_state; |
| /** Sink attached local state variable */ |
| enum snk_attached_local_state_t snk_attached_local_state; |
| /** Source attached local state variable */ |
| enum src_attached_local_state_t src_attached_local_state; |
| /** State machine run event */ |
| struct k_event sm_event; |
| /** PDC settled event */ |
| struct k_event settle_event; |
| |
| /** Transitioning from last_state */ |
| enum pdc_state_t last_state; |
| /* Transitioning to next state */ |
| enum pdc_state_t next_state; |
| /* Return state from sending a command */ |
| enum pdc_state_t send_cmd_return_state; |
| /** PDC Unattached policy */ |
| struct pdc_unattached_policy_t una_policy; |
| /** PDC Sink Attached policy */ |
| struct pdc_snk_attached_policy_t snk_policy; |
| /** PDC Source Attached policy */ |
| struct pdc_src_attached_policy_t src_policy; |
| /** PDC Common policy */ |
| struct pdc_common_policy_t common_policy; |
| |
| /** Cable Property */ |
| union cable_property_t cable_prop; |
| /** PDC version and other information */ |
| struct pdc_info_t info; |
| /** Command mutex */ |
| struct k_mutex mtx; |
| /** PDC command to send */ |
| struct send_cmd_t send_cmd; |
| /** Pointer to current pending command */ |
| struct cmd_t *cmd; |
| /** Bit mask of port events; see PD_STATUS_EVENT_* */ |
| atomic_t port_event; |
| /** CCAPS temp variable used with CMD_PDC_GET_CONNECTOR_CAPABILITY |
| * command */ |
| union connector_capability_t ccaps; |
| /** CONNECTOR_STATUS temp variable used with CONNECTOR_GET_STATUS |
| * command */ |
| union connector_status_t connector_status; |
| /* Sink path status of the port */ |
| bool sink_path_status; |
| /** SINK_PATH_EN temp variable used with CMD_PDC_SET_SINK_PATH command |
| */ |
| bool sink_path_to_send; |
| /** Timeout for a new contract to be negotiated after sending SET_RDO |
| * in the sink entry flow. */ |
| k_timepoint_t new_contract_timeout; |
| /** Cached VBUS voltage in millivolts */ |
| uint16_t vbus; |
| /** UOR variable used with CMD_PDC_SET_UOR command */ |
| union uor_t uor; |
| /** Set the desired power policy, used with CMD_PDC_SET_PDR command */ |
| enum pdc_power_policy pdr_policy; |
| /** Tracks current connection state */ |
| enum attached_state_t attached_state; |
| /** GET_VDO temp variable used with CMD_GET_VDO */ |
| union get_vdo_t vdo_req; |
| /** LPM_PPM_INFO temp variable to hold user buffer pointer */ |
| struct lpm_ppm_info_t *lpm_ppm_info; |
| /** Array used to hold the list of VDO types to request */ |
| uint8_t vdo_type[VDO_NUM]; |
| /** Array used to store VDOs returned from the GET_VDO command */ |
| uint32_t vdo[VDO_NUM]; |
| /** Store the VDO returned for the PD_VDO_DP_CFG */ |
| uint32_t vdo_dp_cfg; |
| /** CONNECTOR_RESET temp variable used with CMD_PDC_CONNECTOR_RESET */ |
| union connector_reset_t connector_reset; |
| /** PD Port Partner discovery state: True if discovery is complete, else |
| * false */ |
| bool discovery_state; |
| /** Charge current while in TypeC Sink state */ |
| uint32_t typec_current_ma; |
| /** Buffer used by public api to receive data from the driver */ |
| uint8_t *public_api_buff; |
| /** Timer to used to verify typec_only vs USB-PD port partner */ |
| struct k_timer typec_only_timer; |
| /** Type of PDOs to get: SNK|SRC from PDC or Port Partner */ |
| struct get_pdo_t get_pdo; |
| /** Variable used to store/set PDC LPM SRC CAPs */ |
| struct set_pdos_t set_pdos; |
| /** Buffer used by public api to receive data from the driver */ |
| uint8_t pch_data_status[5]; |
| /** SET_DRP variable used with CMD_SET_DRP */ |
| enum drp_mode_t drp; |
| enum drp_mode_t drp_read; |
| /** Used by CMD_PDC_SET_SBU_MUX_MODE / CMD_PDC_GET_SBU_MUX_MODE */ |
| enum pdc_sbu_mux_mode sbu_mux_mode; |
| /** Callback */ |
| struct pdc_callback cc_cb; |
| struct pdc_callback ci_cb; |
| /** Callback for PPM */ |
| const struct pdc_callback *ppm_ci_cb; |
| /** Last configured dual role power state */ |
| enum pd_dual_role_states dual_role_state; |
| /** Change indicator bits to clear */ |
| union conn_status_change_bits_t ci; |
| /** Command complete clear bit */ |
| bool cc; |
| /** Vendor defined change indicator bits */ |
| uint16_t vendor_defined_ci; |
| /** System should watch for an HPD wake */ |
| bool hpd_wake_watch; |
| /** Additional change bits to report to PPM. */ |
| union conn_status_change_bits_t overlay_ppm_changes; |
| /** non-UCSI status change reported by PDC */ |
| union vendor_status_change_bits_t vendor_status_change; |
| /** LPM should enable FRS. */ |
| bool frs_enable; |
| /** Store response to the GET_ATTENTION_VDO command */ |
| union get_attention_vdo_t attention_vdo; |
| /** board callback for Type-C port unattach event */ |
| pdc_power_mgmt_board_unattached_cb board_unattach_cb; |
| /** board callback for DP Attention event */ |
| pdc_power_mgmt_board_dp_attention_cb board_dp_attention_cb; |
| /** CMD_SET_BBR_CTS temp variable to communicate the desired state */ |
| bool bbr_cts_enable; |
| /** Battery status */ |
| union battery_status_t bstat; |
| /** Battery capability */ |
| union battery_capability_t bcap; |
| /* Store state of PD power button */ |
| k_timepoint_t pb_long_press; |
| k_timepoint_t pb_press_timeout; |
| /* Alert Data Object */ |
| uint32_t ado; |
| }; |
| |
| /** |
| * @brief Subsystem PDC Data |
| */ |
| struct pdc_data_t { |
| /** This port's thread */ |
| k_tid_t thread; |
| /** This port thread's data */ |
| struct k_thread thread_data; |
| /** Port data */ |
| struct pdc_port_t port; |
| }; |
| |
| /** |
| * @brief Subsystem PDC Config |
| */ |
| struct pdc_config_t { |
| /** Port number for the connector */ |
| uint8_t connector_num; |
| /** |
| * The usbc stack initializes this pointer that creates the |
| * main thread for this port |
| */ |
| void (*create_thread)(const struct device *dev); |
| }; |
| |
| /* Source PDO(s) */ |
| |
| #if defined(CONFIG_PDC_POWER_MGMT_SRC_PDO_PEAK_OCP_100) |
| #define PDO_PEAK_OCP PDO_PEAK_OVERCURR_100 |
| #elif defined(CONFIG_PDC_POWER_MGMT_SRC_PDO_PEAK_OCP_110) |
| #define PDO_PEAK_OCP PDO_PEAK_OVERCURR_110 |
| #elif defined(CONFIG_PDC_POWER_MGMT_SRC_PDO_PEAK_OCP_125) |
| #define PDO_PEAK_OCP PDO_PEAK_OVERCURR_125 |
| #elif defined(CONFIG_PDC_POWER_MGMT_SRC_PDO_PEAK_OCP_150) |
| #define PDO_PEAK_OCP PDO_PEAK_OVERCURR_150 |
| #else |
| #error Invalid peak overcurrent setting |
| #endif |
| |
| static const uint32_t pdo_src_fixed_flags = |
| (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_COMM_CAP | |
| PDO_FIXED_PEAK_CURR(PDO_PEAK_OCP)); |
| |
| static const uint32_t pdc_src_pdo_nominal = |
| PDO_FIXED(5000, 1500, pdo_src_fixed_flags); |
| |
| static const uint32_t pdc_src_pdo_max = |
| PDO_FIXED(5000, 3000, pdo_src_fixed_flags); |
| |
| /* Sink PDO(s) */ |
| enum snk_pdo { |
| SNK_PDO_FIXED_POS, |
| SNK_PDO_BATT_POS, |
| SNK_PDO_VAR_POS, |
| SNK_PDO_COUNT, |
| }; |
| |
| #define PDO_SINK_FIXED_FLAGS \ |
| (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_COMM_CAP) |
| |
| /* Battery PDO covering 5V-5% to the board maximum voltage and current */ |
| #define SNK_PDO_BATT_DEFAULT \ |
| PDO_BATT(4750, CONFIG_PLATFORM_EC_USB_PD_MAX_VOLTAGE_MV, \ |
| CONFIG_PLATFORM_EC_USB_PD_OPERATING_POWER_MW) |
| |
| /* Variable PDO covering 5V-5% to the board maximum voltage and current */ |
| #define SNK_PDO_VAR_DEFAULT \ |
| PDO_VAR(4750, CONFIG_PLATFORM_EC_USB_PD_MAX_VOLTAGE_MV, \ |
| CONFIG_PLATFORM_EC_USB_PD_MAX_CURRENT_MA) |
| |
| struct pdc_pdos_t pdc_snk_pdos = { |
| .pdos = { |
| /* Mandatory fixed 5V PDO */ |
| [SNK_PDO_FIXED_POS] = PDO_FIXED( |
| 5000, |
| MIN((CONFIG_PLATFORM_EC_USB_PD_OPERATING_POWER_MW / 5), |
| CONFIG_PLATFORM_EC_USB_PD_MAX_CURRENT_MA), |
| PDO_SINK_FIXED_FLAGS), |
| [SNK_PDO_BATT_POS] = SNK_PDO_BATT_DEFAULT, |
| [SNK_PDO_VAR_POS] = SNK_PDO_VAR_DEFAULT, |
| }, |
| .pdo_count = SNK_PDO_COUNT, |
| }; |
| |
| static const struct smf_state pdc_states[]; |
| static enum pdc_state_t get_pdc_state(struct pdc_port_t *port); |
| static void set_pdc_state(struct pdc_port_t *port, enum pdc_state_t next_state); |
| static int pdc_subsys_init(const struct device *dev); |
| static void send_cmd_init(struct pdc_port_t *port); |
| static void queue_internal_cmd(struct pdc_port_t *port, enum pdc_cmd_t pdc_cmd); |
| static int queue_public_cmd(struct pdc_port_t *port, enum pdc_cmd_t pdc_cmd); |
| static void init_port_variables(struct pdc_port_t *port, |
| bool reset_charge_manager); |
| static void pd_chipset_startup(void); |
| static void pd_chipset_resume(void); |
| static void pd_chipset_suspend(void); |
| static void pd_chipset_shutdown(void); |
| |
| static void pdc_update_battery_status(struct pdc_port_t *port, bool force); |
| static void pdc_update_battery_capability(struct pdc_port_t *port); |
| static void pdc_print_pdo_info(int port, struct pdc_pdos_t *pdo); |
| |
| static bool should_suspend(struct pdc_port_t *port) |
| { |
| if (!atomic_get(&port->suspend)) { |
| return false; |
| } |
| |
| /* Suspend has been requested. Wait until we are in a safe state. */ |
| |
| enum pdc_state_t current_state = get_pdc_state(port); |
| |
| switch (current_state) { |
| /* Safe states to suspend from */ |
| case PDC_UNATTACHED: |
| case PDC_SNK_ATTACHED: |
| case PDC_SRC_ATTACHED: |
| case PDC_SNK_TYPEC_ONLY: |
| case PDC_SRC_TYPEC_ONLY: |
| return true; |
| |
| /* Allow suspend from the init state if an error has occurred. This |
| * allows suspending when the the PDC is stuck in a bootloader mode |
| * and GET_CONNECTOR_STATUS is repeatedly failing. */ |
| case PDC_INIT: |
| return (port->cmd->error != 0); |
| |
| /* Wait for operation to finish. */ |
| case PDC_SEND_CMD_START: |
| case PDC_SEND_CMD_WAIT: |
| return false; |
| |
| /* No need to transition */ |
| case PDC_SUSPENDED: |
| case PDC_DISABLED: |
| return false; |
| |
| case PDC_INVALID: |
| __ASSERT(0, |
| "current_state is an unreachable state (PDC_INVALID)"); |
| break; |
| case PDC_STATE_COUNT: |
| __ASSERT(0, "Invalid state"); |
| } |
| |
| __builtin_unreachable(); |
| } |
| |
| /** |
| * @brief PDC thread |
| */ |
| static ALWAYS_INLINE void pdc_thread(void *pdc_dev, void *unused1, |
| void *unused2) |
| { |
| const struct device *dev = (const struct device *)pdc_dev; |
| struct pdc_data_t *data = dev->data; |
| struct pdc_port_t *port = &data->port; |
| int rv; |
| |
| while (1) { |
| /* Wait for timeout or event */ |
| rv = k_event_wait(&port->sm_event, PDC_SM_EVENT, false, |
| K_MSEC(LOOP_DELAY_MS)); |
| |
| /* |
| * If k_event_wait returns a non-zero value, then |
| * always clear PDC_SM_EVENT to ensure that the thread goes to |
| * sleep in cases where PDC_SM_EVENT can't be handled |
| * immediately such as when a public cmd is posted, but is |
| * waiting on an internal cmd to be sent. |
| */ |
| if (rv != 0) { |
| k_event_clear(&port->sm_event, PDC_SM_EVENT); |
| } |
| |
| if (should_suspend(port)) { |
| set_pdc_state(port, PDC_SUSPENDED); |
| } |
| |
| /* Run port connection state machine */ |
| smf_run_state(&port->ctx); |
| } |
| } |
| |
| #define PDC_SUBSYS_INIT(inst) \ |
| K_THREAD_STACK_DEFINE(my_stack_area_##inst, \ |
| CONFIG_PDC_POWER_MGMT_STACK_SIZE); \ |
| \ |
| static void create_thread_##inst(const struct device *dev) \ |
| { \ |
| struct pdc_data_t *data = dev->data; \ |
| \ |
| data->thread = k_thread_create( \ |
| &data->thread_data, my_stack_area_##inst, \ |
| K_THREAD_STACK_SIZEOF(my_stack_area_##inst), \ |
| pdc_thread, (void *)dev, 0, 0, \ |
| CONFIG_PDC_POWER_MGMT_THREAD_PRIORTY, K_ESSENTIAL, \ |
| K_FOREVER); \ |
| k_thread_name_set(data->thread, \ |
| "PDC Power Mgmt" STRINGIFY(inst)); \ |
| } \ |
| \ |
| static struct pdc_data_t data_##inst = { \ |
| .port.dev = DEVICE_DT_INST_GET(inst), /* Initial policy read \ |
| from device tree */ \ |
| .port.pdc = COND_CODE_1( \ |
| CONFIG_PDC_RUNTIME_PORT_CONFIG, (NULL), \ |
| DEVICE_DT_GET(DT_INST_PROP_BY_IDX(inst, pdc, 0))), \ |
| .port.una_policy.tcc = DT_STRING_TOKEN( \ |
| DT_INST_PROP(inst, policy), unattached_rp_value), \ |
| .port.una_policy.cc_mode = DT_STRING_TOKEN( \ |
| DT_INST_PROP(inst, policy), unattached_cc_mode), \ |
| .port.una_policy.drp_mode = DT_STRING_TOKEN( \ |
| DT_INST_PROP(inst, policy), unattached_try), \ |
| .port.suspend = ATOMIC_INIT(0), \ |
| .port.dual_role_state = PD_DRP_TOGGLE_ON, \ |
| }; \ |
| \ |
| static struct pdc_config_t config_##inst = { \ |
| .connector_num = USBC_PORT_NEW(DT_DRV_INST(inst)), \ |
| .create_thread = create_thread_##inst, \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(inst, &pdc_subsys_init, NULL, &data_##inst, \ |
| &config_##inst, POST_KERNEL, \ |
| CONFIG_PDC_POWER_MGMT_INIT_PRIORITY, NULL); |
| |
| DT_INST_FOREACH_STATUS_OKAY(PDC_SUBSYS_INIT) |
| |
| #ifndef CONFIG_TEST_NO_PDC_INIT |
| #define PDC_DEVICE_INIT_ONE(node_id) device_init(DEVICE_DT_GET(node_id)); |
| |
| /* Explicitly initialized all ports defined with zephyr,deferred-init */ |
| static int pdc_device_init(void) |
| { |
| DT_FOREACH_STATUS_OKAY(DT_DRV_COMPAT, PDC_DEVICE_INIT_ONE); |
| return 0; |
| } |
| |
| SYS_INIT(pdc_device_init, POST_KERNEL, CONFIG_PDC_POWER_MGMT_INIT_PRIORITY); |
| #endif |
| |
| /* Enforce initialization order constraints. This driver depends on the PDC |
| * driver(s) and also charge manager (the latter is enforced by |
| * `common/charge_manager.c`) |
| */ |
| |
| BUILD_ASSERT(CONFIG_PDC_POWER_MGMT_INIT_PRIORITY > |
| CONFIG_PDC_DRIVER_INIT_PRIORITY, |
| "pdc_power_mgmt must init after PDC drivers"); |
| |
| #define PDC_DATA_INIT(inst) [USBC_PORT_NEW(DT_DRV_INST(inst))] = &data_##inst, |
| |
| /** |
| * @brief data structure used by public API to map port number to PDC_DATA. |
| * The port number is used to index the array. |
| */ |
| static struct pdc_data_t *pdc_data[] = { DT_INST_FOREACH_STATUS_OKAY( |
| PDC_DATA_INIT) }; |
| |
| /** |
| * @brief As a sink, this is the max voltage (in millivolts) we can request |
| * before getting source caps |
| */ |
| static uint32_t pdc_max_request_mv = CONFIG_PLATFORM_EC_USB_PD_MAX_VOLTAGE_MV; |
| |
| /** |
| * @brief As a sink, this is the max power (in milliwatts) needed to operate |
| */ |
| static uint32_t pdc_max_operating_power = |
| CONFIG_PLATFORM_EC_USB_PD_MAX_POWER_MW; |
| |
| static enum pdc_state_t get_pdc_state(struct pdc_port_t *port) |
| { |
| return port->ctx.current - &pdc_states[0]; |
| } |
| |
| static void set_pdc_state(struct pdc_port_t *port, enum pdc_state_t next_state) |
| { |
| if (get_pdc_state(port) != next_state) { |
| port->last_state = get_pdc_state(port); |
| port->next_state = next_state; |
| smf_set_state(SMF_CTX(port), &pdc_states[next_state]); |
| } |
| } |
| |
| #define SAFE_SUBSTATE_NAME(substate_idx, str_array) \ |
| (IN_RANGE((substate_idx), 0, (ARRAY_SIZE((str_array)) - 1)) ? \ |
| (str_array)[(substate_idx)] : \ |
| "Invalid") |
| |
| #define PRINT_STATE_WITH_SUBSTATE(port_num, state_idx, substate_idx, \ |
| substate_strs) \ |
| COND_CODE_1(IS_ENABLED(CONFIG_PDC_POWER_MGMT_LOG_SUBSTATES), \ |
| (LOG_INF("C%d State: %s.%s", (port_num), \ |
| pdc_state_names[(state_idx)], \ |
| SAFE_SUBSTATE_NAME((substate_idx), \ |
| (substate_strs)))), \ |
| (LOG_INF("C%d State: %s (%d)", (port_num), \ |
| pdc_state_names[(state_idx)], (substate_idx)))) |
| |
| #define PRINT_STATE(port_num, state_idx) \ |
| LOG_INF("C%d State: %s", (port_num), pdc_state_names[(state_idx)]) |
| |
| static void set_attached_pdc_state(struct pdc_port_t *port, |
| enum attached_state_t attached_state) |
| { |
| const struct pdc_config_t *const config = port->dev->config; |
| |
| if (attached_state != port->attached_state) { |
| port->attached_state = attached_state; |
| LOG_INF("C%d attached: %s", config->connector_num, |
| attached_state_names[port->attached_state]); |
| } |
| } |
| |
| static void send_cmd_init(struct pdc_port_t *port) |
| { |
| port->send_cmd.public.cmd = CMD_PDC_NONE; |
| port->send_cmd.public.error = 0; |
| port->send_cmd.public.pending = false; |
| port->send_cmd.intern.cmd = CMD_PDC_NONE; |
| port->send_cmd.intern.error = 0; |
| port->send_cmd.intern.pending = false; |
| port->send_cmd.local_state = SEND_CMD_START_ENTRY; |
| } |
| |
| /** |
| * @brief Run a command started by a public api function call |
| */ |
| static void send_pending_public_commands(struct pdc_port_t *port) |
| { |
| /* If we are running public commands, policy state machine must have |
| * finished settling. Post if not already set. |
| */ |
| if (k_event_test(&port->settle_event, PDC_SM_SETTLED_EVENT) == 0) { |
| k_event_post(&port->settle_event, PDC_SM_SETTLED_EVENT); |
| } |
| |
| /* Send a pending public command */ |
| if (port->send_cmd.public.pending) { |
| set_pdc_state(port, PDC_SEND_CMD_START); |
| } |
| } |
| |
| uint32_t pdc_power_mgmt_get_dp_status(int port) |
| { |
| return pdc_data[port]->port.attention_vdo.vdo; |
| } |
| |
| atomic_val_t pdc_power_mgmt_get_events(int port) |
| { |
| return pdc_data[port]->port.port_event; |
| } |
| |
| void pdc_power_mgmt_notify_event(int port, atomic_t event_mask) |
| { |
| atomic_or(&pdc_data[port]->port.port_event, event_mask); |
| pd_send_host_event(PD_EVENT_TYPEC); |
| } |
| |
| void pdc_power_mgmt_clear_event(int port, atomic_t event_mask) |
| { |
| atomic_and(&pdc_data[port]->port.port_event, ~event_mask); |
| } |
| |
| /** |
| * @brief Limits the charge current to zero and invalidates and received Source |
| * PDOS. This function also seeds the charger. |
| * |
| * @param port PDC port instance |
| * @param reset_charger If true, reset the charge manager. Should be false |
| * during system initialization and true otherwise. System initialization |
| * should bypass charge manager reset so that the charger manager is seeded |
| * only after the PDC power management thread initializes the PDC state. |
| */ |
| static void invalidate_charger_settings(struct pdc_port_t *port, |
| bool reset_charge_manager) |
| { |
| const struct pdc_config_t *const config = port->dev->config; |
| |
| if (reset_charge_manager) { |
| typec_set_input_current_limit(config->connector_num, 0, 0); |
| pd_set_input_current_limit(config->connector_num, 0, 0); |
| charge_manager_set_ceil(config->connector_num, |
| CEIL_REQUESTOR_PD, CHARGE_CEIL_NONE); |
| charge_manager_update_dualrole(config->connector_num, |
| CAP_UNKNOWN); |
| } |
| |
| /* Invalidate PDOS */ |
| port->snk_policy.pdo = 0; |
| memset(port->snk_policy.partner_src_pdos.pdos, 0, |
| sizeof(port->snk_policy.partner_src_pdos.pdos)); |
| port->snk_policy.partner_src_pdos.pdo_count = 0; |
| memset(port->src_policy.partner_snk_pdos.pdos, 0, |
| sizeof(port->src_policy.partner_snk_pdos.pdos)); |
| port->src_policy.partner_snk_pdos.pdo_count = 0; |
| } |
| |
| /** |
| * @brief Callers of this function should return immediately because the PDC |
| * state is changed. |
| */ |
| static int queue_public_cmd(struct pdc_port_t *port, enum pdc_cmd_t pdc_cmd) |
| { |
| /* Don't send if still in init state */ |
| enum pdc_state_t s = get_pdc_state(port); |
| |
| if (s == PDC_INIT || s == PDC_SUSPENDED || s == PDC_DISABLED) { |
| return -ENOTCONN; |
| } |
| |
| /* Don't send another public initiated command if one is already pending |
| */ |
| if (port->send_cmd.public.pending) { |
| return -EBUSY; |
| } |
| |
| k_mutex_lock(&port->mtx, K_FOREVER); |
| port->send_cmd.public.cmd = pdc_cmd; |
| port->send_cmd.public.error = 0; |
| port->send_cmd.public.pending = true; |
| k_mutex_unlock(&port->mtx); |
| k_event_post(&port->sm_event, PDC_SM_EVENT); |
| return 0; |
| } |
| |
| /** |
| * @brief Callers of this function should return immediately because the PDC |
| * state is changed. |
| */ |
| static void queue_internal_cmd(struct pdc_port_t *port, enum pdc_cmd_t pdc_cmd) |
| { |
| k_mutex_lock(&port->mtx, K_FOREVER); |
| port->send_cmd.intern.cmd = pdc_cmd; |
| port->send_cmd.intern.error = 0; |
| port->send_cmd.intern.pending = true; |
| k_mutex_unlock(&port->mtx); |
| k_event_post(&port->sm_event, PDC_SM_EVENT); |
| |
| set_pdc_state(port, PDC_SEND_CMD_START); |
| } |
| |
| /** |
| * @brief Trigger a PPM change indication on a port. |
| */ |
| static void trigger_ppm_ci(struct pdc_port_t *port) |
| { |
| const struct pdc_config_t *config = port->dev->config; |
| int port_number = config->connector_num; |
| union cci_event_t cci_event; |
| |
| if (!port->ppm_ci_cb) |
| return; |
| |
| cci_event.connector_change = port_number + 1; |
| port->ppm_ci_cb->handler(port->dev, port->ppm_ci_cb, cci_event); |
| } |
| |
| /** |
| * @brief Reads connector status and takes appropriate action. |
| * |
| * This function should only be called after the completion of the |
| * GET_CONNECTOR_STATUS command. It reads the connect_status, |
| * power_operation_mode, and power_direction bit to determine which state should |
| * be entered. |
| * |
| * Note: The caller should return after this call. |
| */ |
| static void handle_connector_status(struct pdc_port_t *port) |
| { |
| union connector_status_t *status = &port->connector_status; |
| const struct pdc_config_t *config = port->dev->config; |
| int port_number = config->connector_num; |
| union conn_status_change_bits_t conn_status_change_bits; |
| |
| conn_status_change_bits.raw_value = status->raw_conn_status_change_bits; |
| |
| LOG_INF("C%d: Connector Change: 0x%04x, RDO: %d", port_number, |
| conn_status_change_bits.raw_value, RDO_POS(status->rdo)); |
| |
| if (port->sink_path_status != status->sink_path_status) { |
| LOG_DBG("C%d: Sink path status change: %d", port_number, |
| status->sink_path_status); |
| port->sink_path_status = status->sink_path_status; |
| } |
| |
| /* |
| * Set CCI_ACK flag to trigger sending ACK_CC_CI to clear the connector |
| * change indicator bits which were just read as part of the connector |
| * status message. |
| */ |
| if (conn_status_change_bits.raw_value) { |
| port->ci.raw_value = conn_status_change_bits.raw_value; |
| atomic_set_bit(port->cci_flags, CCI_ACK); |
| } |
| |
| /* Trigger PPM CI callback if connector status change was indicated. */ |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_PPM_EVENT)) { |
| /* We need to also overlay any conn status change bits because |
| * they may disappear by the time OPM reads them. |
| */ |
| port->overlay_ppm_changes.raw_value |= |
| conn_status_change_bits.raw_value; |
| trigger_ppm_ci(port); |
| k_event_post(&port->settle_event, |
| PDC_PPM_CONNECTOR_STATUS_READY); |
| } |
| |
| if (conn_status_change_bits.pd_reset_complete) { |
| LOG_INF("C%d: Reset complete indicator", port_number); |
| pdc_power_mgmt_notify_event(port_number, |
| PD_STATUS_EVENT_HARD_RESET); |
| |
| atomic_set(&port->hard_reset_sent, true); |
| } |
| |
| if (!status->connect_status) { |
| /* Port is not connected */ |
| port->vbus = 0; |
| set_pdc_state(port, PDC_UNATTACHED); |
| return; |
| } |
| |
| /* Update cached VBUS voltage (voltage_scale in 5mV increments) */ |
| port->vbus = status->voltage_reading * status->voltage_scale * 5; |
| |
| switch (status->power_operation_mode) { |
| case USB_DEFAULT_OPERATION: |
| port->typec_current_ma = 500; |
| break; |
| case BC_OPERATION: |
| port->typec_current_ma = 500; |
| break; |
| case PD_OPERATION: |
| port->typec_current_ma = 0; |
| if (conn_status_change_bits.supported_cam) { |
| atomic_set_bit(port->cci_flags, CCI_CAM_CHANGE); |
| LOG_INF("C%d: CAM change", port_number); |
| } |
| |
| if (conn_status_change_bits.attention) { |
| atomic_set_bit(port->cci_flags, CCI_ATTENTION); |
| LOG_INF("C%d: Attention", port_number); |
| } |
| |
| if (conn_status_change_bits.supported_provider_caps && |
| port->attached_state == SNK_ATTACHED_STATE && |
| port->snk_attached_local_state >= SNK_ATTACHED_GET_PDOS) { |
| /* Source caps have changed. Set the sink- |
| * attached state machine back to the get PDO |
| * substate. This will cause PDOs to be re- |
| * evaluated and the sink path to get enabled |
| * again. */ |
| atomic_set_bit(port->snk_policy.flags, |
| SNK_POLICY_NEW_SRC_CAPS_AVAILABLE); |
| LOG_INF("C%d: New SRC CAPs available", port_number); |
| k_event_post(&port->sm_event, PDC_SM_EVENT); |
| } |
| |
| if (status->power_direction) { |
| if (conn_status_change_bits.negotiated_power_level) { |
| /* |
| * When we are a source, if the partner sends |
| * a new Request, the PDC sets the negotiated |
| * power level change. Request sink caps again. |
| * This flow is atypical for most sink devices, |
| * but it is behavior PD testers exercise. |
| */ |
| atomic_set_bit(port->src_policy.flags, |
| SRC_POLICY_GET_SINK_CAPS); |
| } |
| /* Port partner is a sink device |
| */ |
| set_pdc_state(port, PDC_SRC_ATTACHED); |
| return; |
| } else { |
| /* Port partner is a source |
| * device */ |
| set_pdc_state(port, PDC_SNK_ATTACHED); |
| return; |
| } |
| break; |
| case USB_TC_CURRENT_1_5A: |
| port->typec_current_ma = 1500; |
| break; |
| case USB_TC_CURRENT_3A: |
| port->typec_current_ma = 3000; |
| break; |
| case USB_TC_CURRENT_5A: |
| port->typec_current_ma = 5000; |
| break; |
| } |
| |
| /* TypeC only connection */ |
| if (status->power_direction) { |
| /* Port partner is a Typec Sink device */ |
| set_pdc_state(port, PDC_SRC_TYPEC_ONLY); |
| return; |
| } else { |
| /* Port partner is a Typec Source device */ |
| if (conn_status_change_bits.pwr_operation_mode) { |
| atomic_set_bit(port->snk_policy.flags, |
| SNK_POLICY_UPDATE_TYPEC_CURRENT); |
| } |
| set_pdc_state(port, PDC_SNK_TYPEC_ONLY); |
| return; |
| } |
| } |
| |
| /** |
| * @brief Reads vendor defined connector status change bits, this should only |
| * be used to process events which are not supported by UCSI. |
| */ |
| static void handle_vendor_status(struct pdc_port_t *port) |
| { |
| if (port->vendor_status_change.alert_received) { |
| atomic_set_bit(port->common_policy.flags, |
| COMMON_POLICY_GET_ALERT); |
| k_event_post(&port->sm_event, PDC_SM_EVENT); |
| } |
| } |
| |
| /** |
| * @brief Trigger connector status change on PPM |
| * |
| * The UCSI spec says that certain commands with side-effects (like SET_PDR) do |
| * not generate status change interrupts if the host was the one that caused the |
| * change. This can create a state de-sync between the EC and OS so we should |
| * fake some connector changes for capture these side effects. |
| * |
| */ |
| static void trigger_ppm_status_change(struct pdc_port_t *port) |
| { |
| union conn_status_change_bits_t status = { .raw_value = 0 }; |
| |
| /* No status change on command error. */ |
| if (!port->cmd || port->cmd->error) { |
| return; |
| } |
| |
| switch (port->cmd->cmd) { |
| case CMD_PDC_SET_PDR: |
| status.pwr_direction = 1; |
| break; |
| case CMD_PDC_SET_UOR: |
| status.connector_partner = 1; |
| break; |
| case CMD_PDC_SET_PDOS: |
| status.supported_provider_caps = 1; |
| break; |
| case CMD_PDC_SET_SINK_PATH: |
| status.sink_path_status_change = 1; |
| break; |
| case CMD_PDC_READ_POWER_LEVEL: |
| status.negotiated_power_level = 1; |
| break; |
| |
| /* For all other commands, no need to trigger as there shouldn't be |
| * side-effects to connector status. |
| */ |
| default: |
| return; |
| } |
| |
| /* If trigger CI, we should also refresh the connector status. */ |
| atomic_set_bit(port->cci_flags, CCI_EVENT); |
| k_event_post(&port->sm_event, PDC_SM_EVENT); |
| |
| port->overlay_ppm_changes.raw_value |= status.raw_value; |
| trigger_ppm_ci(port); |
| } |
| |
| /** |
| * @brief This function is used to format the GET_VDO command which is used to |
| * extract VID, PID, and Product Type values from the port partners Discovery |
| * Identity response message. |
| */ |
| static void discovery_info_init(struct pdc_port_t *port) |
| { |
| int i; |
| |
| port->vdo_req.raw_value = 0; |
| /* Request VDOs from port partner */ |
| port->vdo_req.vdo_origin = VDO_ORIGIN_SOP; |
| port->vdo_req.num_vdos = ARRAY_SIZE(vdo_discovery_list); |
| |
| /* Create the list of VDO types being requested */ |
| for (i = 0; i < ARRAY_SIZE(vdo_discovery_list); i++) { |
| port->vdo_type[i] = vdo_discovery_list[i]; |
| port->vdo[i] = INVALID_VDO_VALUE; |
| } |
| |
| /* Clear the DP Config VDO, which stores the DP pin assignment */ |
| port->vdo_dp_cfg = 0; |
| /* Clear attention VDO which contains DP status */ |
| memset(&port->attention_vdo, 0, sizeof(port->attention_vdo)); |
| } |
| |
| /** |
| * @brief This function gets the correct pointer for pdc_pdos_t struct |
| * |
| * These structs are used to store SRC/SNK CAPs PDOs of the partner device. |
| * The pdo_req must specify PARTNER_PDO and the CAP type. |
| */ |
| static struct pdc_pdos_t *get_pdc_pdos_ptr(struct pdc_port_t *port, |
| struct get_pdo_t *pdo_req) |
| { |
| struct pdc_pdos_t *pdc_pdos; |
| |
| __ASSERT(pdo_req->pdo_source == PARTNER_PDO, "Invalid PDO source: %d", |
| pdo_req->pdo_source); |
| |
| if (pdo_req->pdo_type == SINK_PDO) { |
| pdc_pdos = &port->src_policy.partner_snk_pdos; |
| } else { |
| pdc_pdos = &port->snk_policy.partner_src_pdos; |
| } |
| |
| return pdc_pdos; |
| } |
| |
| static bool run_common_policies(struct pdc_port_t *port) |
| { |
| if (atomic_test_and_clear_bit(port->common_policy.flags, |
| COMMON_POLICY_SET_POWER_STATE)) { |
| /* Send new AP power state to PDC */ |
| queue_internal_cmd(port, CMD_PDC_SET_AP_POWER_STATE); |
| return true; |
| } |
| |
| if (atomic_test_and_clear_bit(port->common_policy.flags, |
| COMMON_POLICY_SET_RP)) { |
| /* Check if Rp value needs to be adjusted */ |
| queue_internal_cmd(port, CMD_PDC_SET_POWER_LEVEL); |
| return true; |
| } |
| |
| if (atomic_test_and_clear_bit(port->common_policy.flags, |
| COMMON_POLICY_GET_ALERT)) { |
| /* Read latest ADO */ |
| queue_internal_cmd(port, CMD_PDC_GET_ALERT); |
| return true; |
| } |
| |
| /* Note: COMMON_POLICY_SET_SBU_MUX_TO_FORCED_DEBUG is checked in the |
| * INIT state. */ |
| |
| return false; |
| } |
| |
| static void run_unattached_policies(struct pdc_port_t *port) |
| { |
| if (run_common_policies(port)) { |
| return; |
| } |
| |
| if (atomic_test_and_clear_bit(port->una_policy.flags, |
| UNA_POLICY_DRP_MODE)) { |
| /* Set DRP current policy */ |
| queue_internal_cmd(port, CMD_PDC_SET_DRP); |
| return; |
| } else if (atomic_test_and_clear_bit(port->una_policy.flags, |
| UNA_POLICY_CC_MODE)) { |
| /* Set CC PULL Resistor and TrySrc or TrySnk */ |
| queue_internal_cmd(port, CMD_PDC_SET_CCOM); |
| if (port->una_policy.cc_mode == CCOM_DRP) { |
| /* Apply DRP mode after setting CCOM_DRP |
| * SET_CCOM doesn't specify what DRP mode we'll enter |
| * so we should follow up by setting it ourselves. |
| */ |
| atomic_set_bit(port->una_policy.flags, |
| UNA_POLICY_DRP_MODE); |
| } |
| return; |
| } else if (atomic_test_and_clear_bit(port->una_policy.flags, |
| UNA_POLICY_TCC)) { |
| /* Set RP current policy */ |
| queue_internal_cmd(port, CMD_PDC_SET_POWER_LEVEL); |
| /* Make sure new Rp value is applied */ |
| atomic_set_bit(port->una_policy.flags, UNA_POLICY_CC_MODE); |
| return; |
| } else if (atomic_test_and_clear_bit(port->una_policy.flags, |
| UNA_POLICY_UPDATE_SRC_CAPS)) { |
| /* Ensure the next time a PD capable SNK connects, we offer |
| * a safe PDO. |
| */ |
| port->set_pdos = (struct set_pdos_t){ |
| .count = 1, |
| .type = SOURCE_PDO, |
| .pdos = { port->src_policy.lpm_src_pdo }, |
| }; |
| |
| queue_internal_cmd(port, CMD_PDC_SET_PDOS); |
| return; |
| } |
| |
| send_pending_public_commands(port); |
| } |
| |
| static bool should_swap_to_source(struct pdc_port_t *port) |
| { |
| const struct pdc_config_t *config = port->dev->config; |
| int port_num = config->connector_num; |
| |
| /* If all of the following are true, swap to source: |
| * a) Source caps were received from the port partner |
| * b) Port partner supports DRP and does not offer unconstrained power |
| * c) Port isn't the active charging port. |
| */ |
| |
| if (port->snk_policy.partner_src_pdos.pdo_count == 0) { |
| LOG_INF("C%d: %s: Remain sink because partner has no source caps", |
| port_num, __func__); |
| return false; |
| } |
| |
| /* Only the fixed 5V PDO at index 0 has the UP and DRP bits set */ |
| uint32_t vsafe_5v_pdo = port->snk_policy.partner_src_pdos.pdos[0]; |
| |
| if (vsafe_5v_pdo & PDO_FIXED_GET_UNCONSTRAINED_PWR || |
| !(vsafe_5v_pdo & PDO_FIXED_GET_DRP)) { |
| LOG_INF("C%d: %s: Remain sink because partner has unconstrained " |
| "power or is not DRP-capable (vSafe5v PDO=0x%08x, " |
| "active PDO=0x%08x)", |
| port_num, __func__, vsafe_5v_pdo, port->snk_policy.pdo); |
| return false; |
| } |
| |
| if (charge_manager_get_active_charge_port() == port_num) { |
| LOG_INF("C%d: %s: Remain sink because this is the active " |
| "charging port", |
| port_num, __func__); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static void handle_attention_vdo(struct pdc_port_t *port) |
| { |
| const struct pdc_config_t *config = port->dev->config; |
| int port_num = config->connector_num; |
| /* Check for an HPD wake on DP Status. The conditions are... |
| * a) Device is suspended. |
| * b) Port is currently using an alternate mode. |
| * c) Port entered suspend in DP Alt Mode with HPD_LVL low. |
| * d) Updated DP Status has HPD_LVL high. |
| */ |
| if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND) && |
| (port->connector_status.conn_partner_flags & |
| CONNECTOR_PARTNER_FLAG_ALTERNATE_MODE) && |
| port->hpd_wake_watch && |
| PD_VDO_DPSTS_HPD_LVL(port->attention_vdo.vdo)) { |
| host_set_single_event(EC_HOST_EVENT_USB_MUX); |
| } |
| |
| if (port->board_dp_attention_cb) { |
| port->board_dp_attention_cb(port_num, port->attention_vdo.vdo); |
| } |
| } |
| |
| static void handle_alert(struct pdc_port_t *port, uint32_t ado) |
| { |
| const struct pdc_config_t *config = port->dev->config; |
| int port_num = config->connector_num; |
| enum ado_extended_alert_event_type event_type; |
| |
| if (pdc_power_mgmt_pd_get_data_role(port_num) != PD_ROLE_DFP || |
| !(ado & ADO_EXTENDED_ALERT_EVENT)) { |
| return; |
| } |
| |
| event_type = ado & ADO_EXTENDED_ALERT_EVENT_TYPE; |
| if (event_type == ADO_POWER_BUTTON_PRESS) { |
| port->pb_press_timeout = sys_timepoint_calc( |
| K_SECONDS(PD_POWER_BUTTON_PRESS_TIMEOUT)); |
| port->pb_long_press = sys_timepoint_calc( |
| K_SECONDS(PD_POWER_BUTTON_LONG_PRESS)); |
| } else if (event_type == ADO_POWER_BUTTON_RELEASE) { |
| /* If the device is on and a long power button press is received |
| * shutdown the device. Otherwise, simulate a short power button |
| * press. Release alerts without a corresponding press are |
| * treated as short power button presses. |
| */ |
| if (!sys_timepoint_expired(port->pb_press_timeout)) { |
| if (sys_timepoint_expired(port->pb_long_press) && |
| (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND) | |
| chipset_in_state(CHIPSET_STATE_ON))) { |
| chipset_force_shutdown(CHIPSET_SHUTDOWN_BUTTON); |
| } else { |
| pdc_power_mgmt_simulate_power_button_press(500); |
| } |
| } |
| port->pb_press_timeout = sys_timepoint_calc(K_FOREVER); |
| port->pb_long_press = sys_timepoint_calc(K_FOREVER); |
| } |
| } |
| |
| static void pdc_send_sink_pdos(struct pdc_port_t *port) |
| { |
| const struct pdc_config_t *config = port->dev->config; |
| int port_num = config->connector_num; |
| |
| /* Set sink PDO(s) that reflects this board's max voltage and current */ |
| port->set_pdos = (struct set_pdos_t){ |
| .type = SINK_PDO, |
| .count = pdc_snk_pdos.pdo_count, |
| }; |
| |
| pdc_print_pdo_info(port_num, &pdc_snk_pdos); |
| |
| memcpy(port->set_pdos.pdos, pdc_snk_pdos.pdos, |
| pdc_snk_pdos.pdo_count * sizeof(uint32_t)); |
| |
| queue_internal_cmd(port, CMD_PDC_SET_PDOS); |
| } |
| |
| static void run_snk_policies(struct pdc_port_t *port) |
| { |
| const struct pdc_config_t *config = port->dev->config; |
| int port_num = config->connector_num; |
| |
| if (run_common_policies(port)) { |
| return; |
| } |
| |
| if (atomic_test_and_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_SET_ACTIVE_CHARGE_PORT)) { |
| port->snk_attached_local_state = SNK_ATTACHED_SET_SINK_PATH; |
| return; |
| } else if (atomic_test_and_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_SWAP_TO_SRC)) { |
| /* Become a source. Set the external swap policy to current |
| * (sink) setting so that it remains unchanged if the swap |
| * fails. */ |
| port->pdr_policy = PDC_POWER_POLICY_SOURCE( |
| port->snk_policy.accept_power_role_swap); |
| queue_internal_cmd(port, CMD_PDC_SET_PDR); |
| return; |
| } else if (atomic_test_and_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_NEW_POWER_REQUEST)) { |
| port->snk_attached_local_state = SNK_ATTACHED_EVALUATE_PDOS; |
| return; |
| } else if (atomic_test_and_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_EVAL_SWAP_TO_SRC)) { |
| bool should_swap = should_swap_to_source(port); |
| |
| if (should_swap) { |
| atomic_set_bit( |
| pdc_data[port_num]->port.snk_policy.flags, |
| SNK_POLICY_SWAP_TO_SRC); |
| } |
| |
| LOG_INF("C%d: SNK_POLICY_EVAL_SWAP_TO_SRC: Prefer %s role.", |
| config->connector_num, should_swap ? "source" : "sink"); |
| return; |
| } else if (atomic_test_and_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_UPDATE_ALLOW_PR_SWAP)) { |
| /* Remain a sink but update external swap policy */ |
| port->pdr_policy = PDC_POWER_POLICY_SINK( |
| port->snk_policy.accept_power_role_swap); |
| queue_internal_cmd(port, CMD_PDC_SET_PDR); |
| return; |
| } else if (atomic_test_and_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_UPDATE_SRC_CAPS)) { |
| /* Update the LPM with the correct SRC PDO in case there |
| * is a power role swap. |
| */ |
| port->set_pdos = (struct set_pdos_t){ |
| .count = 1, |
| .type = SOURCE_PDO, |
| .pdos = { port->src_policy.lpm_src_pdo }, |
| }; |
| |
| queue_internal_cmd(port, CMD_PDC_SET_PDOS); |
| return; |
| } else if (atomic_test_and_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_UPDATE_BATTERY_STATUS)) { |
| /* Update the PDC with the correct battery status. */ |
| queue_internal_cmd(port, CMD_PDC_SET_BATTERY_STATUS); |
| return; |
| } else if (atomic_test_and_clear_bit( |
| port->snk_policy.flags, |
| SNK_POLICY_UPDATE_BATTERY_CAPABILITY)) { |
| /* Update the PDC with the correct battery capabilities. */ |
| queue_internal_cmd(port, CMD_PDC_SET_BATTERY_CAPABILITY); |
| return; |
| } |
| |
| if (pdc_power_mgmt_get_frs_hw_supported(port_num)) { |
| /* When FRS is supported, the source current limits need |
| * rebalancing when a DRP source is attached. |
| */ |
| if (atomic_test_and_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_EVAL_SNK_FIXED_PDO)) { |
| struct get_pdo_t get_partner_snk_pdo = { |
| .pdo_source = PARTNER_PDO, |
| .pdo_type = SINK_PDO, |
| }; |
| uint32_t sink_fixed_pdo = |
| get_pdc_pdos_ptr(port, &get_partner_snk_pdo) |
| ->pdos[0]; |
| pdc_dpm_eval_sink_fixed_pdo(port_num, sink_fixed_pdo); |
| return; |
| } |
| } |
| |
| send_pending_public_commands(port); |
| } |
| |
| static void run_typec_snk_policies(struct pdc_port_t *port) |
| { |
| const struct pdc_config_t *config = port->dev->config; |
| /* Note - hard resets specifically not checked for here. |
| |
| * We don't expect hard resets while connected to a non-PD |
| * partner. |
| */ |
| |
| if (run_common_policies(port)) { |
| return; |
| } |
| |
| if (atomic_test_and_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_SET_ACTIVE_CHARGE_PORT)) { |
| /* Check if we are the active charge port */ |
| port->sink_path_to_send = |
| charge_manager_get_active_charge_port() == |
| config->connector_num; |
| if (port->sink_path_to_send) { |
| /* Set ACOKREF to 5000 */ |
| charge_manager_set_acokref(5000); |
| } |
| queue_internal_cmd(port, CMD_PDC_SET_SINK_PATH); |
| } else if (atomic_test_and_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_UPDATE_SRC_CAPS)) { |
| /* Ensure the next time a PD capable SNK connects, we offer |
| * a safe PDO. |
| */ |
| port->set_pdos = (struct set_pdos_t){ |
| .count = 1, |
| .type = SOURCE_PDO, |
| .pdos = { port->src_policy.lpm_src_pdo }, |
| }; |
| |
| queue_internal_cmd(port, CMD_PDC_SET_PDOS); |
| } else if (atomic_test_and_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_UPDATE_TYPEC_CURRENT)) { |
| if (port->sink_path_status) { |
| charge_manager_set_supplier(config->connector_num, |
| CHARGE_SUPPLIER_TYPEC); |
| } |
| |
| pd_set_input_current_limit(config->connector_num, 0, 0); |
| typec_set_input_current_limit(config->connector_num, |
| port->typec_current_ma, 5000); |
| |
| charge_manager_update_dualrole(config->connector_num, |
| CAP_DEDICATED); |
| } else { |
| send_pending_public_commands(port); |
| } |
| } |
| |
| static void run_src_policies(struct pdc_port_t *port) |
| { |
| const struct pdc_config_t *config = port->dev->config; |
| int port_num = config->connector_num; |
| |
| if (run_common_policies(port)) { |
| return; |
| } |
| |
| if (atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_SWAP_TO_SNK)) { |
| /* Become a sink. Set the external swap policy to current |
| * (source) setting so that it remains unchanged if the swap |
| * fails. */ |
| port->pdr_policy = PDC_POWER_POLICY_SINK( |
| port->src_policy.accept_power_role_swap); |
| queue_internal_cmd(port, CMD_PDC_SET_PDR); |
| return; |
| } else if (atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_FORCE_SNK)) { |
| queue_internal_cmd(port, CMD_PDC_SET_CCOM); |
| return; |
| } else if (atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_EVAL_SNK_FIXED_PDO)) { |
| /* Adjust source current limits if necessary */ |
| pdc_dpm_eval_sink_fixed_pdo( |
| port_num, port->src_policy.partner_snk_pdos.pdos[0]); |
| |
| /* If the partner is DRP capable, request source caps */ |
| if (port->src_policy.partner_snk_pdos.pdos[0] & |
| PDO_FIXED_GET_DRP) { |
| atomic_set_bit(port->src_policy.flags, |
| SRC_POLICY_GET_SRC_CAPS); |
| } |
| |
| return; |
| } else if (atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_GET_SINK_CAPS)) { |
| /* Request up to 4 pdos to honor USCI 6.5.15 Get PDOs - Number |
| * of PDOs to return starting from the PDO Offset. The number of |
| * PDOs to return is the value in this field plus 1. |
| */ |
| if (!port->get_pdo.updating) { |
| port->get_pdo.num_pdos = PDO_MAX_OBJECTS; |
| port->get_pdo.pdo_offset = PDO_OFFSET_0; |
| port->get_pdo.updating = true; |
| } |
| if (port->get_pdo.num_pdos > UCSI_GET_PDOS_MAX_NUM) { |
| /* More sink caps needed, rearm this path. */ |
| atomic_set_bit(port->src_policy.flags, |
| SRC_POLICY_GET_SINK_CAPS); |
| } else { |
| /* All sink caps will be known following the next |
| * queued GET_PDOS operation. Trigger source policy |
| * evaluation. |
| */ |
| atomic_set_bit(port->src_policy.flags, |
| SRC_POLICY_EVAL_SNK_FIXED_PDO); |
| port->get_pdo.updating = false; |
| } |
| port->get_pdo.pdo_type = SINK_PDO; |
| port->get_pdo.pdo_source = PARTNER_PDO; |
| queue_internal_cmd(port, CMD_PDC_GET_PDOS); |
| return; |
| } else if (atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_UPDATE_SRC_CAPS)) { |
| /* Update the PDC SRC_CAP message */ |
| port->set_pdos = (struct set_pdos_t){ |
| .count = 1, |
| .type = SOURCE_PDO, |
| .pdos = { port->src_policy.lpm_src_pdo }, |
| }; |
| |
| queue_internal_cmd(port, CMD_PDC_SET_PDOS); |
| /* |
| * After sending new SRC_CAP message, get the RDO from the port |
| * partner to see if the current limit can be adjusted. |
| */ |
| atomic_set_bit(port->src_policy.flags, SRC_POLICY_GET_RDO); |
| return; |
| } else if (atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_GET_RDO)) { |
| /* Get the RDO from the port partner */ |
| queue_internal_cmd(port, CMD_PDC_GET_RDO); |
| return; |
| } else if (IS_ENABLED(CONFIG_USBC_PDC_TBT_SUPPORTED) && |
| atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_TBT_RESET)) { |
| pdc_dpm_tbt_set_reset_ongoing(port_num); |
| port->connector_reset.raw_value = 0; |
| port->connector_reset.reset_type = PD_HARD_RESET; |
| queue_internal_cmd(port, CMD_PDC_CONNECTOR_RESET); |
| return; |
| } else if (atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_GET_SRC_CAPS)) { |
| /* |
| * For evaluating swap to sink, we only request the partner's |
| * first SRC PDO. |
| */ |
| port->get_pdo.num_pdos = 1; |
| port->get_pdo.pdo_offset = PDO_OFFSET_0; |
| port->get_pdo.pdo_type = SOURCE_PDO; |
| port->get_pdo.pdo_source = PARTNER_PDO; |
| port->get_pdo.updating = false; |
| |
| atomic_set_bit(port->src_policy.flags, |
| SRC_POLICY_EVAL_SRC_PDOS); |
| queue_internal_cmd(port, CMD_PDC_GET_PDOS); |
| return; |
| } else if (atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_EVAL_SRC_PDOS)) { |
| /* Request a swap to sink if the partner has unconstained power |
| */ |
| if (port->snk_policy.partner_src_pdos.pdos[0] & |
| PDO_FIXED_GET_UNCONSTRAINED_PWR) { |
| atomic_set_bit(port->src_policy.flags, |
| SRC_POLICY_SWAP_TO_SNK); |
| } |
| |
| return; |
| } else if (atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_UPDATE_ALLOW_PR_SWAP)) { |
| /* Remain a source but update external swap policy */ |
| port->pdr_policy = PDC_POWER_POLICY_SOURCE( |
| port->src_policy.accept_power_role_swap); |
| queue_internal_cmd(port, CMD_PDC_SET_PDR); |
| return; |
| } else if (atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_UPDATE_BATTERY_STATUS)) { |
| /* Update the PDC with the correct battery status. */ |
| queue_internal_cmd(port, CMD_PDC_SET_BATTERY_STATUS); |
| return; |
| } else if (atomic_test_and_clear_bit( |
| port->src_policy.flags, |
| SRC_POLICY_UPDATE_BATTERY_CAPABILITY)) { |
| /* Update the PDC with the correct battery capabilities. */ |
| queue_internal_cmd(port, CMD_PDC_SET_BATTERY_CAPABILITY); |
| return; |
| } |
| |
| send_pending_public_commands(port); |
| } |
| |
| static void run_typec_src_policies(struct pdc_port_t *port) |
| { |
| if (run_common_policies(port)) { |
| return; |
| } |
| |
| if (atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_FORCE_SNK)) { |
| queue_internal_cmd(port, CMD_PDC_SET_CCOM); |
| } else if (atomic_test_and_clear_bit(port->src_policy.flags, |
| SRC_POLICY_UPDATE_SRC_CAPS)) { |
| /* Ensure the next time a PD capable SNK connects, we offer |
| * a safe PDO. |
| */ |
| port->set_pdos = (struct set_pdos_t){ |
| .count = 1, |
| .type = SOURCE_PDO, |
| .pdos = { port->src_policy.lpm_src_pdo }, |
| }; |
| |
| queue_internal_cmd(port, CMD_PDC_SET_PDOS); |
| } else { |
| send_pending_public_commands(port); |
| } |
| } |
| |
| /** |
| * @brief Entering unattached state |
| */ |
| static void pdc_unattached_entry(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *config = port->dev->config; |
| int port_number = config->connector_num; |
| |
| PRINT_STATE_WITH_SUBSTATE(port_number, get_pdc_state(port), |
| port->unattached_local_state, |
| unattached_local_state_names); |
| |
| set_attached_pdc_state(port, UNATTACHED_STATE); |
| port->send_cmd.intern.pending = false; |
| |
| /* Clear any previously set cable property information */ |
| port->cable_prop.raw_value[0] = 0; |
| port->cable_prop.raw_value[1] = 0; |
| |
| /* Ensure VDOs aren't valid from previous connection */ |
| discovery_info_init(port); |
| |
| /* Clear cached VBUS */ |
| port->vbus = 0; |
| |
| /* Reset PD button */ |
| port->ado = 0; |
| port->pb_press_timeout = sys_timepoint_calc(K_FOREVER); |
| port->pb_long_press = sys_timepoint_calc(K_FOREVER); |
| |
| if (get_pdc_state(port) != port->send_cmd_return_state) { |
| invalidate_charger_settings(port, true); |
| port->unattached_local_state = UNATTACHED_SET_SINK_PATH_OFF; |
| /* Update source current limit policy */ |
| pdc_dpm_remove_sink(port_number); |
| pdc_dpm_remove_source(port_number); |
| |
| /* Clear all events except for disconnect. */ |
| pdc_power_mgmt_clear_event(port_number, |
| BIT_MASK(PD_STATUS_EVENT_COUNT)); |
| pdc_power_mgmt_notify_event(port_number, |
| PD_STATUS_EVENT_DISCONNECTED); |
| |
| if (IS_ENABLED(CONFIG_PDC_POWER_MGMT_USB_MUX)) { |
| usb_mux_set(port_number, USB_PD_MUX_NONE, |
| USB_SWITCH_DISCONNECT, |
| /* port is unattacehd, not meaningful */ |
| POLARITY_CC1); |
| } |
| |
| if (port->board_unattach_cb) { |
| port->board_unattach_cb(port_number); |
| } |
| } |
| } |
| |
| /** |
| * @brief Run unattached state |
| */ |
| static enum smf_state_result pdc_unattached_run(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| |
| /* The CCI_EVENT is set to re-query connector status, so check the |
| * connector status and take the appropriate action. |
| */ |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_EVENT)) { |
| queue_internal_cmd(port, CMD_PDC_GET_CONNECTOR_STATUS); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_ACK)) { |
| queue_internal_cmd(port, CMD_PDC_ACK_CC_CI); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_SET_SINK_PDOS)) { |
| const struct pdc_config_t *config = port->dev->config; |
| LOG_INF("C%d: unattached send sink PDOs", |
| config->connector_num); |
| pdc_send_sink_pdos(port); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| switch (port->unattached_local_state) { |
| case UNATTACHED_SET_SINK_PATH_OFF: |
| port->sink_path_to_send = false; |
| port->unattached_local_state = UNATTACHED_RUN; |
| queue_internal_cmd(port, CMD_PDC_SET_SINK_PATH); |
| return SMF_EVENT_HANDLED; |
| case UNATTACHED_RUN: |
| run_unattached_policies(port); |
| break; |
| case UNATTACHED_INVALID: |
| __builtin_unreachable(); |
| } |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /** |
| * @brief Entering source attached state |
| */ |
| static void pdc_src_attached_entry(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *config = port->dev->config; |
| int port_number = config->connector_num; |
| |
| PRINT_STATE_WITH_SUBSTATE(port_number, get_pdc_state(port), |
| port->src_attached_local_state, |
| src_attached_local_state_names); |
| |
| set_attached_pdc_state(port, SRC_ATTACHED_STATE); |
| |
| port->send_cmd.intern.pending = false; |
| |
| if (get_pdc_state(port) != port->send_cmd_return_state) { |
| invalidate_charger_settings(port, true); |
| port->src_attached_local_state = SRC_ATTACHED_SET_SINK_PATH_OFF; |
| port->get_pdo = (struct get_pdo_t){ 0 }; |
| |
| /* We always want to evalulate sink caps when we a source. */ |
| atomic_set_bit(port->src_policy.flags, |
| SRC_POLICY_GET_SINK_CAPS); |
| |
| if (IS_ENABLED(CONFIG_PDC_POWER_MGMT_USB_MUX)) { |
| usb_mux_set( |
| port_number, USB_PD_MUX_USB_ENABLED, |
| USB_SWITCH_CONNECT, |
| pdc_power_mgmt_pd_get_polarity(port_number)); |
| } |
| |
| /* Update the PDC with the correct battery status. */ |
| pdc_update_battery_capability(port); |
| pdc_update_battery_status(port, true); |
| } |
| |
| /* Clear a piece of sink policy as it is no longer relevant in the |
| * sourcing state. |
| */ |
| atomic_clear_bit(port->snk_policy.flags, SNK_POLICY_EVAL_SWAP_TO_SRC); |
| } |
| |
| /** |
| * @brief Run source attached state |
| */ |
| static enum smf_state_result pdc_src_attached_run(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| |
| /* The CCI_EVENT is set to re-query connector status, so check the |
| * connector status and take the appropriate action. |
| */ |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_EVENT)) { |
| queue_internal_cmd(port, CMD_PDC_GET_CONNECTOR_STATUS); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_VENDOR_EVENT)) { |
| queue_internal_cmd(port, CMD_PDC_GET_VENDOR_STATUS); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_ACK)) { |
| queue_internal_cmd(port, CMD_PDC_ACK_CC_CI); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_CAM_CHANGE)) { |
| queue_internal_cmd(port, CMD_PDC_GET_PD_VDO_DP_CFG_SELF); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_ATTENTION)) { |
| queue_internal_cmd(port, CMD_PDC_GET_ATTENTION_VDO); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_SET_SINK_PDOS)) { |
| const struct pdc_config_t *config = port->dev->config; |
| LOG_INF("C%d: SRC attached send sink PDOs", |
| config->connector_num); |
| pdc_send_sink_pdos(port); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /* TODO: b/319643480 - Brox: implement SRC policies */ |
| |
| switch (port->src_attached_local_state) { |
| case SRC_ATTACHED_SET_SINK_PATH_OFF: |
| port->sink_path_to_send = false; |
| port->src_attached_local_state = |
| SRC_ATTACHED_GET_CONNECTOR_CAPABILITY; |
| queue_internal_cmd(port, CMD_PDC_SET_SINK_PATH); |
| return SMF_EVENT_HANDLED; |
| case SRC_ATTACHED_GET_CONNECTOR_CAPABILITY: |
| port->src_attached_local_state = |
| SRC_ATTACHED_SET_DR_SWAP_POLICY; |
| queue_internal_cmd(port, CMD_PDC_GET_CONNECTOR_CAPABILITY); |
| return SMF_EVENT_HANDLED; |
| case SRC_ATTACHED_SET_DR_SWAP_POLICY: |
| port->src_attached_local_state = |
| SRC_ATTACHED_SET_PR_SWAP_POLICY; |
| /* TODO read from DT */ |
| port->uor.swap_to_dfp = 1; |
| port->uor.swap_to_ufp = 0; |
| port->uor.accept_dr_swap = 0; |
| queue_internal_cmd(port, CMD_PDC_SET_UOR); |
| return SMF_EVENT_HANDLED; |
| case SRC_ATTACHED_SET_PR_SWAP_POLICY: |
| port->src_attached_local_state = SRC_ATTACHED_READ_POWER_LEVEL; |
| /* TODO: read from DT */ |
| port->pdr_policy = PDC_POWER_POLICY_SOURCE( |
| port->src_policy.accept_power_role_swap); |
| queue_internal_cmd(port, CMD_PDC_SET_PDR); |
| atomic_clear_bit(port->src_policy.flags, |
| SRC_POLICY_UPDATE_ALLOW_PR_SWAP); |
| return SMF_EVENT_HANDLED; |
| case SRC_ATTACHED_READ_POWER_LEVEL: |
| port->src_attached_local_state = SRC_ATTACHED_GET_VDO; |
| queue_internal_cmd(port, CMD_PDC_READ_POWER_LEVEL); |
| return SMF_EVENT_HANDLED; |
| case SRC_ATTACHED_GET_VDO: |
| port->src_attached_local_state = |
| SRC_ATTACHED_GET_CABLE_PROPERTY; |
| queue_internal_cmd(port, CMD_PDC_GET_VDO); |
| return SMF_EVENT_HANDLED; |
| case SRC_ATTACHED_GET_CABLE_PROPERTY: |
| port->src_attached_local_state = SRC_ATTACHED_RUN; |
| queue_internal_cmd(port, CMD_PDC_GET_CABLE_PROPERTY); |
| return SMF_EVENT_HANDLED; |
| case SRC_ATTACHED_RUN: |
| run_src_policies(port); |
| break; |
| case SRC_ATTACHED_INVALID: |
| __builtin_unreachable(); |
| } |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /** |
| * @brief Entering sink attached state |
| */ |
| static void pdc_snk_attached_entry(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *config = port->dev->config; |
| int port_number = config->connector_num; |
| |
| PRINT_STATE_WITH_SUBSTATE(port_number, get_pdc_state(port), |
| port->snk_attached_local_state, |
| snk_attached_local_state_names); |
| |
| set_attached_pdc_state(port, SNK_ATTACHED_STATE); |
| |
| port->send_cmd.intern.pending = false; |
| if (get_pdc_state(port) != port->send_cmd_return_state) { |
| /* Reset local state */ |
| port->snk_attached_local_state = |
| SNK_ATTACHED_GET_CONNECTOR_CAPABILITY; |
| port->get_pdo = (struct get_pdo_t){ 0 }; |
| |
| /* If we were just a SRC, tell the DPM that the |
| * attached sink has been disconnected. |
| */ |
| pdc_dpm_remove_sink(port_number); |
| |
| if (IS_ENABLED(CONFIG_PDC_POWER_MGMT_USB_MUX)) { |
| usb_mux_set( |
| port_number, USB_PD_MUX_USB_ENABLED, |
| USB_SWITCH_CONNECT, |
| pdc_power_mgmt_pd_get_polarity(port_number)); |
| } |
| |
| /* Update the PDC with the correct battery status. */ |
| pdc_update_battery_capability(port); |
| pdc_update_battery_status(port, true); |
| } |
| } |
| |
| static void pdc_print_pdo_info(int port, struct pdc_pdos_t *pdo) |
| { |
| uint32_t max_ma, max_mv, max_mw, min_mv; |
| const char *type_str = NULL; |
| |
| if (pdo->pdo_count == 0 || pdo->pdo_count > PDO_MAX_OBJECTS) { |
| LOG_ERR("C%d: invalid pdo count detected %d", port, |
| pdo->pdo_count); |
| return; |
| } |
| |
| /* Prints a table of PDOs with key fields extracted |
| * |
| * C0: Raw Type mV mA mW DRP UP USB DRD FRS |
| * C0: PDO1: 20019000, FIX 5000 0 0 Y - - - - |
| * C0: PDO2: 00000000, FIX 0 0 0 - - - - - |
| * ... |
| */ |
| |
| LOG_INF("C%d: Raw Type mV mA mW " |
| "DRP UP USB DRD FRS", |
| port); |
| |
| for (int i = 0; i < pdo->pdo_count; i++) { |
| uint32_t p = pdo->pdos[i]; |
| |
| if (p == 0) { |
| /* Empty PDO */ |
| LOG_INF("C%d: PDO%d: %08x", port, i + 1, p); |
| continue; |
| } |
| |
| pd_extract_pdo_power_unclamped(p, &max_ma, &max_mv, &min_mv); |
| max_mw = max_ma * max_mv / 1000; |
| |
| switch (p & PDO_TYPE_MASK) { |
| case PDO_TYPE_FIXED: |
| type_str = "FIX"; |
| LOG_INF("C%d: PDO%d: %08x, %s %-5u %-4u %-6u " |
| "%c %c %c %c %c", |
| port, i + 1, p, type_str, max_mv, max_ma, |
| max_mw, p & PDO_FIXED_DUAL_ROLE ? 'Y' : '-', |
| p & PDO_FIXED_UNCONSTRAINED ? 'Y' : '-', |
| p & PDO_FIXED_COMM_CAP ? 'Y' : '-', |
| p & PDO_FIXED_DATA_SWAP ? 'Y' : '-', |
| p & PDO_FIXED_FRS_CURR_MASK ? 'Y' : '-'); |
| continue; |
| case PDO_TYPE_BATTERY: |
| type_str = "BAT"; |
| break; |
| case PDO_TYPE_VARIABLE: |
| type_str = "VAR"; |
| break; |
| /* LCOV_EXCL_START - unreachable as we filter augmented PDOs */ |
| case PDO_TYPE_AUGMENTED: |
| type_str = "AUG"; |
| break; |
| } |
| /* LCOV_EXCL_STOP */ |
| |
| /* Battery, variable, and augmented PDOs have voltage |
| * ranges but no flags. |
| */ |
| LOG_INF("C%d: PDO%d: %08x, %s %-5umV-%-5umV, %-5umA", port, |
| i + 1, p, type_str, min_mv, max_mv, max_ma); |
| } |
| } |
| |
| static bool pdc_snk_policy_is_pdo_same(struct pdc_port_t *port, |
| uint32_t selected_port, |
| uint32_t selected_pdo, int pdo_index) |
| { |
| const struct pdc_config_t *const config = port->dev->config; |
| |
| return (config->connector_num == selected_port && |
| port->snk_policy.pdo == selected_pdo && |
| port->snk_policy.pdo_index == (pdo_index + 1)); |
| } |
| |
| static void |
| pdc_snk_attached_send_set_rdo(struct pdc_port_t *port, |
| struct pdc_snk_attached_policy_t *snk_policy) |
| { |
| const struct pdc_config_t *const config = port->dev->config; |
| uint32_t max_ma, max_mv, max_mw, max_mw_pdo, unused; |
| uint32_t flags = RDO_COMM_CAP; |
| |
| /* Get the unclamped PDO voltage and current to determine |
| * whether we need to set the capability mismatch bit if |
| * less power is offered than our operating requirement. |
| */ |
| pd_extract_pdo_power_unclamped(snk_policy->pdo, &max_ma, &max_mv, |
| &unused); |
| max_mw_pdo = max_ma * max_mv / 1000; |
| |
| if (pdc_max_request_mv < CONFIG_PLATFORM_EC_USB_PD_MAX_VOLTAGE_MV) { |
| LOG_INF("C%d: max voltage (%d mV) limited by policy, don't set cap mismatch", |
| config->connector_num, pdc_max_request_mv); |
| } else if (max_mw_pdo < pdc_max_operating_power) { |
| flags |= RDO_CAP_MISMATCH; |
| } |
| |
| /* Extract Current, Voltage, and calculate Power. Current is |
| * clamped to the board maximum here so that the RDO and charge |
| * manager are given the correct board operating current. |
| */ |
| pd_extract_pdo_power(snk_policy->pdo, &max_ma, &max_mv, &unused); |
| max_mw = max_ma * max_mv / 1000; |
| |
| /* Set RDO to send */ |
| if ((snk_policy->pdo & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) { |
| snk_policy->rdo_to_send = |
| RDO_BATT(snk_policy->pdo_index, max_mw, max_mw, flags); |
| } else { |
| /* Fixed or variable RDO. */ |
| snk_policy->rdo_to_send = |
| RDO_FIXED(snk_policy->pdo_index, max_ma, max_ma, flags); |
| } |
| |
| LOG_INF("C%d: Send RDO: %d (%08x), battery_is_present=%d, mismatch=%d", |
| config->connector_num, RDO_POS(snk_policy->rdo_to_send), |
| snk_policy->rdo_to_send, battery_is_present(), |
| !!(flags & RDO_CAP_MISMATCH)); |
| LOG_INF("C%d: Power request: %umA, %umV, %umW, " |
| "pdo_max=%umW, board_max=%umW", |
| config->connector_num, max_ma, max_mv, max_mw, max_mw_pdo, |
| pdc_max_operating_power); |
| queue_internal_cmd(port, CMD_PDC_SET_RDO); |
| } |
| |
| static void pdc_snk_seed_charge_manager(struct pdc_port_t *port, uint32_t pdo) |
| { |
| const struct pdc_config_t *const config = port->dev->config; |
| uint32_t max_ma, max_mv, max_mw, unused; |
| |
| pd_extract_pdo_power(pdo, &max_ma, &max_mv, &unused); |
| max_mw = max_ma * max_mv / 1000; |
| |
| /* Only the fixed 5V PDO at index 0 has the UP and DRP bits set */ |
| uint32_t vsafe_5v_pdo = port->snk_policy.partner_src_pdos.pdos[0]; |
| |
| LOG_INF("C%d: Available charging (%sconstrained)", |
| config->connector_num, |
| (vsafe_5v_pdo & PDO_FIXED_GET_UNCONSTRAINED_PWR) ? "un" : ""); |
| LOG_INF(" PDO: %08x (RDO pos %d)", pdo, |
| RDO_POS(port->connector_status.rdo)); |
| LOG_INF(" V: %d", max_mv); |
| LOG_INF(" I: %d", max_ma); |
| LOG_INF(" P: %d", max_mw); |
| |
| if (port->sink_path_status) { |
| charge_manager_set_supplier(config->connector_num, |
| CHARGE_SUPPLIER_PD); |
| } |
| |
| typec_set_input_current_limit(config->connector_num, 0, 0); |
| pd_set_input_current_limit(config->connector_num, max_ma, max_mv); |
| charge_manager_set_ceil(config->connector_num, CEIL_REQUESTOR_PD, |
| max_ma); |
| |
| if (((PDO_GET_TYPE(pdo) == PDO_TYPE_FIXED) && |
| (!(vsafe_5v_pdo & PDO_FIXED_GET_DRP) || |
| (vsafe_5v_pdo & PDO_FIXED_GET_UNCONSTRAINED_PWR))) || |
| (max_mw >= PD_DRP_CHARGE_POWER_MIN)) { |
| /* Port partner is a robust power source, meeting one or more of |
| * these conditions: |
| * |
| * 1) Able to provide PD_DRP_CHARGE_POWER_MIN watts or more, |
| * which defaults to 27W. This is likely a dock or large |
| * battery bank. |
| * 2) Partner advertised unconstrained power and is not DRP |
| * (this can only be determined from fixed PDOs). This is |
| * likely an AC power adapter. |
| */ |
| |
| LOG_INF("C%d: Partner is a robust source (CAP_DEDICATED)", |
| config->connector_num); |
| charge_manager_update_dualrole(config->connector_num, |
| CAP_DEDICATED); |
| } else { |
| /* Port partner is a weak power source, such as a phone or small |
| * battery power bank. Charge manager may not initiate charging |
| * unless the user opts in to through the UI, or `chgoverride` |
| * is sent on the EC console. |
| */ |
| |
| LOG_INF("C%d: Partner is a weak source (CAP_DUALROLE). Charging " |
| "may not start automatically!", |
| config->connector_num); |
| charge_manager_update_dualrole(config->connector_num, |
| CAP_DUALROLE); |
| } |
| } |
| |
| static uint8_t pdc_get_snk_path_en_mask(void) |
| { |
| uint8_t snk_path_en_mask = 0; |
| |
| for (int port = 0; port < CONFIG_USB_PD_PORT_MAX_COUNT; port++) { |
| WRITE_BIT(snk_path_en_mask, port, |
| pdc_data[port]->port.sink_path_status); |
| } |
| |
| return snk_path_en_mask; |
| } |
| |
| /** Return values for pdc_snk_attached_evaluate_pdos(), describing what |
| * action was taken by the function. |
| */ |
| enum eval_pdo_outcome { |
| /** Multiple sink paths are enabled. Hold in current state until only |
| * one sink path is active. */ |
| EVAL_PDO_OUTCOME_SINK_PATH_STATE, |
| /** Keep the current RDO. A new RDO has NOT been sent, so proceed */ |
| EVAL_PDO_OUTCOME_RETAIN_CURRENT_RDO, |
| /** A new RDO has been sent. Wait for the the PDC to re-negotiate before |
| * proceeding. */ |
| EVAL_PDO_OUTCOME_SEND_NEW_RDO, |
| }; |
| |
| /** |
| * @brief Evaluate PDOs and send RDO when only one (or none) sink path is |
| * enabled. The sink path is disabled on non-preferred ports. |
| * |
| * @return enum eval_pdo_outcome |
| */ |
| static enum eval_pdo_outcome |
| pdc_snk_attached_evaluate_pdos(struct pdc_port_t *port) |
| { |
| const struct pdc_config_t *const config = port->dev->config; |
| int pdo_index = 0, selected_port; |
| uint32_t selected_pdo; |
| |
| pdc_print_pdo_info(config->connector_num, |
| &port->snk_policy.partner_src_pdos); |
| |
| pdo_index = pd_select_best_pdo(PDO_MAX_OBJECTS, |
| port->snk_policy.partner_src_pdos.pdos, |
| pdc_max_request_mv, &selected_pdo); |
| |
| /* No valid PDOs found, move to next state */ |
| if (pdo_index == -1) { |
| LOG_INF("C%d: No valid PDOs found. Keep current RDO.", |
| config->connector_num); |
| return EVAL_PDO_OUTCOME_RETAIN_CURRENT_RDO; |
| } |
| |
| selected_port = charge_manager_get_active_charge_port(); |
| |
| if (pdc_snk_policy_is_pdo_same(port, selected_port, selected_pdo, |
| pdo_index)) { |
| LOG_INF("C%d: Retaining PDO[%d]=0x%08X", config->connector_num, |
| pdo_index, selected_pdo); |
| return EVAL_PDO_OUTCOME_RETAIN_CURRENT_RDO; |
| } |
| |
| uint8_t sink_path_mask = pdc_get_snk_path_en_mask(); |
| |
| /* if sink path is enabled on more than 1 port */ |
| if (sink_path_mask != 0 && !IS_POWER_OF_TWO(sink_path_mask)) { |
| LOG_INF("C%d: sink_path_mask=0x%X, selected_port=%d", |
| config->connector_num, sink_path_mask, selected_port); |
| |
| /* Disable sink path if we're not the selected port. */ |
| if (!IS_BIT_SET(sink_path_mask, selected_port)) { |
| port->sink_path_to_send = false; |
| queue_internal_cmd(port, CMD_PDC_SET_SINK_PATH); |
| } |
| |
| /* Remain in current state until only one sink path is enabled*/ |
| return EVAL_PDO_OUTCOME_SINK_PATH_STATE; |
| } |
| |
| /* if sink path is enabled, battery is not present, and AP is ON, |
| * do not send RDO. Proceed to seed charge manager with current RDO. |
| */ |
| if (port->sink_path_status && battery_is_present() != BP_YES && |
| !chipset_in_state(CHIPSET_STATE_HARD_OFF)) { |
| LOG_INF("C%d: Dead battery detected. Keep current RDO.", |
| config->connector_num); |
| return EVAL_PDO_OUTCOME_RETAIN_CURRENT_RDO; |
| } |
| |
| /* Only one sink path is enabled, safe to update RDO */ |
| port->snk_policy.pdo = |
| port->snk_policy.partner_src_pdos.pdos[pdo_index]; |
| port->snk_policy.pdo_index = pdo_index + 1; |
| |
| pdc_snk_attached_send_set_rdo(port, &port->snk_policy); |
| return EVAL_PDO_OUTCOME_SEND_NEW_RDO; |
| } |
| |
| static bool pdc_is_rdo_valid(const union connector_status_t *cs) |
| { |
| LOG_INF("%s: status=%d, power_op_mode=%d, RDO_POS=%d", __func__, |
| cs->connect_status, cs->power_operation_mode, RDO_POS(cs->rdo)); |
| |
| return (cs->connect_status == 1 && |
| cs->power_operation_mode == PD_OPERATION); |
| } |
| |
| /** |
| * @brief Set the sink path handler for SNK_ATTACHED state |
| * |
| * Ensures the sink path is only enabled on one port at a time. |
| * |
| * @return true - Proceed to next state |
| * false - Remain in current state. |
| */ |
| static bool pdc_snk_attached_set_sink_path(struct pdc_port_t *port) |
| { |
| const struct pdc_config_t *const config = port->dev->config; |
| int selected_port = charge_manager_get_active_charge_port(); |
| uint8_t sink_path_mask = pdc_get_snk_path_en_mask(); |
| bool enable = selected_port == config->connector_num; |
| |
| LOG_INF("C%d: sink_path_mask=0x%X, selected_port=%d, enable=%d", |
| config->connector_num, sink_path_mask, selected_port, enable); |
| |
| if (enable) { |
| if (sink_path_mask == 0) { |
| /* No other ports have sink path enabled, |
| * proceed to enable */ |
| int pdo_mv; |
| int pdo_ma; |
| int pdo_mw; |
| pd_extract_pdo_power_unclamped(port->snk_policy.pdo, |
| &pdo_ma, &pdo_mv, |
| &pdo_mw); |
| charge_manager_set_acokref(pdo_mv); |
| |
| port->sink_path_to_send = true; |
| queue_internal_cmd(port, CMD_PDC_SET_SINK_PATH); |
| return true; |
| } else if (port->sink_path_status) { |
| /* Already enabled proceed to next state */ |
| return true; |
| } else { |
| /* Sink path enabled on other ports, stay in current |
| * state and wait until they are disabled */ |
| return false; |
| } |
| } else { |
| /* Disabling sink path, no need to wait */ |
| port->sink_path_to_send = false; |
| queue_internal_cmd(port, CMD_PDC_SET_SINK_PATH); |
| return true; |
| } |
| } |
| |
| /** |
| * @brief Run sink attached state. |
| */ |
| static enum smf_state_result pdc_snk_attached_run(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *config = port->dev->config; |
| union conn_status_change_bits_t conn_status_change_bits; |
| |
| /* The CCI_EVENT is set to re-query connector status, so check the |
| * connector status and take the appropriate action. |
| */ |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_EVENT)) { |
| queue_internal_cmd(port, CMD_PDC_GET_CONNECTOR_STATUS); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_VENDOR_EVENT)) { |
| queue_internal_cmd(port, CMD_PDC_GET_VENDOR_STATUS); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_ACK)) { |
| queue_internal_cmd(port, CMD_PDC_ACK_CC_CI); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_CAM_CHANGE)) { |
| queue_internal_cmd(port, CMD_PDC_GET_PD_VDO_DP_CFG_SELF); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_ATTENTION)) { |
| queue_internal_cmd(port, CMD_PDC_GET_ATTENTION_VDO); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_SET_SINK_PDOS)) { |
| LOG_INF("C%d: SNK attached send sink PDOs", |
| config->connector_num); |
| pdc_send_sink_pdos(port); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /* If the attached charger sends new source caps, the PDC will |
| * turn off the sink path and we need to get the current |
| * source PDOs from the partner. |
| */ |
| if (port->snk_attached_local_state >= SNK_ATTACHED_GET_PDOS && |
| atomic_test_and_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_NEW_SRC_CAPS_AVAILABLE)) { |
| port->get_pdo = (struct get_pdo_t){ 0 }; |
| invalidate_charger_settings(port, true); |
| /* Update the local state immediately without requiring |
| * a reschedule of the thread. |
| */ |
| port->snk_attached_local_state = SNK_ATTACHED_GET_PDOS; |
| } |
| |
| switch (port->snk_attached_local_state) { |
| case SNK_ATTACHED_GET_CONNECTOR_CAPABILITY: |
| port->snk_attached_local_state = |
| SNK_ATTACHED_SET_DR_SWAP_POLICY; |
| queue_internal_cmd(port, CMD_PDC_GET_CONNECTOR_CAPABILITY); |
| return SMF_EVENT_HANDLED; |
| case SNK_ATTACHED_SET_DR_SWAP_POLICY: |
| port->snk_attached_local_state = |
| SNK_ATTACHED_SET_PR_SWAP_POLICY; |
| /* TODO read from DT */ |
| port->uor.swap_to_dfp = 1; |
| port->uor.swap_to_ufp = 0; |
| port->uor.accept_dr_swap = 0; |
| queue_internal_cmd(port, CMD_PDC_SET_UOR); |
| return SMF_EVENT_HANDLED; |
| case SNK_ATTACHED_SET_PR_SWAP_POLICY: |
| port->snk_attached_local_state = SNK_ATTACHED_GET_VDO; |
| /* TODO: read from DT */ |
| port->pdr_policy = PDC_POWER_POLICY_SINK( |
| port->snk_policy.accept_power_role_swap); |
| queue_internal_cmd(port, CMD_PDC_SET_PDR); |
| atomic_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_UPDATE_ALLOW_PR_SWAP); |
| return SMF_EVENT_HANDLED; |
| case SNK_ATTACHED_GET_VDO: |
| port->snk_attached_local_state = SNK_ATTACHED_GET_PDOS; |
| queue_internal_cmd(port, CMD_PDC_GET_VDO); |
| return SMF_EVENT_HANDLED; |
| case SNK_ATTACHED_GET_PDOS: |
| /* Request up to 4 pdos to honor USCI 6.5.15 Get PDOs - Number |
| * of PDOs to return starting from the PDO Offset. The number of |
| * PDOs to return is the value in this field plus 1. |
| */ |
| atomic_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_NEW_POWER_REQUEST); |
| atomic_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_NEW_SRC_CAPS_AVAILABLE); |
| if (!port->get_pdo.updating) { |
| port->get_pdo.num_pdos = PDO_MAX_OBJECTS; |
| port->get_pdo.pdo_offset = PDO_OFFSET_0; |
| port->get_pdo.updating = true; |
| } |
| if (port->get_pdo.num_pdos > UCSI_GET_PDOS_MAX_NUM) { |
| port->snk_attached_local_state = SNK_ATTACHED_GET_PDOS; |
| } else { |
| port->snk_attached_local_state = |
| (port->sink_path_status ? |
| SNK_ATTACHED_SYNC_CHARGE_MGR : |
| SNK_ATTACHED_EVALUATE_PDOS); |
| port->get_pdo.updating = false; |
| } |
| port->get_pdo.pdo_type = SOURCE_PDO; |
| port->get_pdo.pdo_source = PARTNER_PDO; |
| queue_internal_cmd(port, CMD_PDC_GET_PDOS); |
| return SMF_EVENT_HANDLED; |
| case SNK_ATTACHED_EVALUATE_PDOS: |
| /* Remain in EVAL PDOs if more than one PDC has the sink path |
| * enabled. Once the charge manager is seeded, it will select |
| * the best port and disable the sink path on all but the best |
| * port. |
| */ |
| switch (pdc_snk_attached_evaluate_pdos(port)) { |
| case EVAL_PDO_OUTCOME_SINK_PATH_STATE: |
| /* Remain in this substate until only a single sink path |
| * is active. */ |
| break; |
| case EVAL_PDO_OUTCOME_RETAIN_CURRENT_RDO: |
| /* No RDO changes. Proceed directly to seeding charge |
| * manager. */ |
| port->snk_attached_local_state = |
| SNK_ATTACHED_SYNC_CHARGE_MGR; |
| break; |
| case EVAL_PDO_OUTCOME_SEND_NEW_RDO: |
| /* A new RDO was sent. Wait for the PD contract to be |
| * negotiated by the PDC. */ |
| port->new_contract_timeout = |
| sys_timepoint_calc(NEW_CONTRACT_TIMEOUT); |
| port->snk_attached_local_state = |
| SNK_ATTACHED_WAIT_FOR_CONTRACT; |
| break; |
| } |
| return SMF_EVENT_HANDLED; |
| case SNK_ATTACHED_WAIT_FOR_CONTRACT: |
| /* Poll GET_CONNECTOR_STATUS until the `negotiated_power_level` |
| * bit is set. */ |
| conn_status_change_bits.raw_value = |
| port->connector_status.raw_conn_status_change_bits; |
| |
| LOG_INF("C%d: Wait for negotiated_power_level (%04x) " |
| "or correct RDO (req %d, curr %d)", |
| config->connector_num, |
| conn_status_change_bits.raw_value, |
| RDO_POS(port->snk_policy.rdo_to_send), |
| RDO_POS(port->connector_status.rdo)); |
| |
| if (conn_status_change_bits.negotiated_power_level || |
| RDO_POS(port->snk_policy.rdo_to_send) == |
| RDO_POS(port->connector_status.rdo)) { |
| /* New contract is in place. Seed charge_manager. */ |
| port->snk_attached_local_state = |
| SNK_ATTACHED_SYNC_CHARGE_MGR; |
| } else if (sys_timepoint_expired(port->new_contract_timeout)) { |
| /* Timed out waiting for a contract. Proceed with the |
| * RDO chosen by the PDC, even if suboptimal. Charge |
| * manager will be seeded with the actual active RDO. */ |
| LOG_ERR("C%d: New contract cannot be established. " |
| "Proceed with current contract.", |
| config->connector_num); |
| port->snk_attached_local_state = |
| SNK_ATTACHED_SYNC_CHARGE_MGR; |
| } else { |
| /* Poll GET_CONNECTOR_STATUS again */ |
| queue_internal_cmd(port, CMD_PDC_GET_CONNECTOR_STATUS); |
| } |
| return SMF_EVENT_HANDLED; |
| case SNK_ATTACHED_SYNC_CHARGE_MGR: |
| /* Update Charge Manager with PDO/RDO the PDC has negotiated or |
| * that PDC Power Mgmt has evaluated. If Charge manager is not |
| * seeded, remain in this state. Charge manager will |
| * invoke set_active_charge_port after being seeded, moving |
| * PDC Power Mgmt onto SET_SINK_PATH state. |
| */ |
| if (pdc_is_rdo_valid(&port->connector_status)) { |
| uint32_t pdo_index = |
| RDO_POS(port->connector_status.rdo) - 1; |
| pdc_snk_seed_charge_manager( |
| port, port->snk_policy.partner_src_pdos |
| .pdos[pdo_index]); |
| } |
| port->snk_attached_local_state = |
| (charge_manager_is_seeded() ? |
| SNK_ATTACHED_SET_SINK_PATH : |
| SNK_ATTACHED_SYNC_CHARGE_MGR); |
| return SMF_EVENT_HANDLED; |
| case SNK_ATTACHED_SET_SINK_PATH: |
| |
| if (pdc_snk_attached_set_sink_path(port)) { |
| if (pdc_power_mgmt_get_frs_hw_supported( |
| config->connector_num)) { |
| if (port->ccaps.op_mode_drp) { |
| port->snk_attached_local_state = |
| SNK_ATTACHED_GET_SINK_PDO; |
| } else { |
| pdc_dpm_remove_source( |
| config->connector_num); |
| port->snk_attached_local_state = |
| SNK_ATTACHED_GET_CABLE_PROPERTY; |
| } |
| } else { |
| port->snk_attached_local_state = |
| SNK_ATTACHED_GET_CABLE_PROPERTY; |
| } |
| } |
| |
| return SMF_EVENT_HANDLED; |
| case SNK_ATTACHED_GET_SINK_PDO: |
| port->snk_attached_local_state = |
| SNK_ATTACHED_GET_CABLE_PROPERTY; |
| |
| if (pdc_power_mgmt_get_frs_hw_supported( |
| config->connector_num)) { |
| /* |
| * We only care about the first fixed PDO for FRS, so |
| * only ask for the first sink PDO from the partner. |
| */ |
| port->get_pdo.num_pdos = 1; |
| port->get_pdo.pdo_offset = PDO_OFFSET_0; |
| port->get_pdo.pdo_type = SINK_PDO; |
| port->get_pdo.pdo_source = PARTNER_PDO; |
| port->get_pdo.updating = false; |
| |
| /* Evaluate SNK CAP after it's been retrieved from the |
| * PDC */ |
| atomic_set_bit(port->snk_policy.flags, |
| SNK_POLICY_EVAL_SNK_FIXED_PDO); |
| |
| queue_internal_cmd(port, CMD_PDC_GET_PDOS); |
| return SMF_EVENT_HANDLED; |
| } |
| /* If |
| * !pdc_power_mgmt_get_frs_hw_supported(config->connector_num), |
| * fallthrough */ |
| __fallthrough; |
| case SNK_ATTACHED_GET_CABLE_PROPERTY: |
| port->snk_attached_local_state = SNK_ATTACHED_READ_POWER_LEVEL; |
| queue_internal_cmd(port, CMD_PDC_GET_CABLE_PROPERTY); |
| return SMF_EVENT_HANDLED; |
| case SNK_ATTACHED_READ_POWER_LEVEL: |
| port->snk_attached_local_state = SNK_ATTACHED_RUN; |
| queue_internal_cmd(port, CMD_PDC_READ_POWER_LEVEL); |
| return SMF_EVENT_HANDLED; |
| case SNK_ATTACHED_RUN: |
| /* Hard Reset could disable Sink FET. Re-enable it */ |
| if (atomic_get(&port->hard_reset_sent)) { |
| atomic_clear(&port->hard_reset_sent); |
| port->snk_attached_local_state = |
| SNK_ATTACHED_SET_SINK_PATH; |
| } else { |
| run_snk_policies(port); |
| } |
| break; |
| case SNK_ATTACHED_INVALID: |
| __builtin_unreachable(); |
| } |
| return SMF_EVENT_HANDLED; |
| } |
| |
| static void pdc_send_cmd_start_entry(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| |
| port->send_cmd_return_state = port->last_state; |
| port->send_cmd.wait_counter = 0; |
| |
| if (port->send_cmd.intern.pending) { |
| port->cmd = &port->send_cmd.intern; |
| } else { |
| port->cmd = &port->send_cmd.public; |
| } |
| } |
| |
| /** |
| * @brief Return a string name for an `enum pdc_power_policy` value |
| */ |
| static const char *pdc_power_policy_to_string(enum pdc_power_policy policy) |
| { |
| switch (policy) { |
| case PDC_POWER_POLICY_SINK_ALLOW_SWAP: |
| return "SINK_ALLOW_SWAP"; |
| case PDC_POWER_POLICY_SINK_DISALLOW_SWAP: |
| return "SINK_DISALLOW_SWAP"; |
| case PDC_POWER_POLICY_SOURCE_ALLOW_SWAP: |
| return "SOURCE_ALLOW_SWAP"; |
| case PDC_POWER_POLICY_SOURCE_DISALLOW_SWAP: |
| return "SOURCE_DISALLOW_SWAP"; |
| } |
| return "Unknown"; |
| } |
| |
| static int send_pdc_cmd(struct pdc_port_t *port) |
| { |
| int rv; |
| const struct pdc_config_t *const config = port->dev->config; |
| uint32_t *rdo; |
| |
| LOG_INF("C%d: Send %s (%d) %s", config->connector_num, |
| pdc_cmd_names[port->cmd->cmd], port->cmd->cmd, |
| (port->cmd == &port->send_cmd.intern) ? "internal" : "public"); |
| |
| /* Send PDC command via driver API */ |
| switch (port->cmd->cmd) { |
| case CMD_PDC_RESET: |
| rv = pdc_reset(port->pdc); |
| break; |
| case CMD_PDC_GET_INFO: |
| rv = pdc_get_info(port->pdc, &port->info, true); |
| break; |
| case CMD_PDC_SET_POWER_LEVEL: |
| rv = pdc_set_power_level(port->pdc, port->una_policy.tcc); |
| break; |
| case CMD_PDC_SET_CCOM: |
| LOG_INF("C%d: SET_CCOM=%s", config->connector_num, |
| get_ccom_name(port->una_policy.cc_mode)); |
| rv = pdc_set_ccom(port->pdc, port->una_policy.cc_mode); |
| break; |
| case CMD_PDC_SET_DRP: |
| LOG_INF("C%d: SET_DRP_MODE=%s", config->connector_num, |
| get_drp_mode_name(port->drp)); |
| rv = pdc_set_drp_mode(port->pdc, port->drp); |
| break; |
| case CMD_PDC_GET_DRP: |
| rv = pdc_get_drp_mode(port->pdc, &port->drp_read); |
| break; |
| case CMD_PDC_GET_PDOS: |
| rv = pdc_get_pdos(port->pdc, port->get_pdo.pdo_type, |
| port->get_pdo.pdo_offset, |
| min(port->get_pdo.num_pdos, |
| UCSI_GET_PDOS_MAX_NUM), |
| port->get_pdo.pdo_source, |
| get_pdc_pdos_ptr(port, &port->get_pdo)->pdos + |
| port->get_pdo.pdo_offset); |
| if (!rv && port->get_pdo.num_pdos > UCSI_GET_PDOS_MAX_NUM) { |
| port->get_pdo.num_pdos -= UCSI_GET_PDOS_MAX_NUM; |
| port->get_pdo.pdo_offset = PDO_OFFSET_4; |
| } |
| break; |
| case CMD_PDC_GET_RDO: |
| /* RDO from LPM or port partner depending on power role */ |
| if (port->attached_state == SRC_ATTACHED_STATE) { |
| rdo = &port->src_policy.rdo; |
| } else { |
| rdo = &port->snk_policy.rdo; |
| } |
| rv = pdc_get_rdo(port->pdc, rdo); |
| break; |
| case CMD_PDC_SET_RDO: |
| rv = pdc_set_rdo(port->pdc, port->snk_policy.rdo_to_send); |
| break; |
| case CMD_PDC_SET_SINK_PATH: |
| LOG_INF("C%d: sink_path_to_send=%d, chg_mgr_active_charge_port=%d", |
| config->connector_num, port->sink_path_to_send, |
| charge_manager_get_active_charge_port()); |
| rv = pdc_set_sink_path(port->pdc, port->sink_path_to_send); |
| break; |
| case CMD_PDC_READ_POWER_LEVEL: |
| rv = pdc_read_power_level(port->pdc); |
| break; |
| case CMD_PDC_GET_CONNECTOR_CAPABILITY: |
| rv = pdc_get_connector_capability(port->pdc, &port->ccaps); |
| break; |
| case CMD_PDC_SET_UOR: |
| rv = pdc_set_uor(port->pdc, port->uor); |
| break; |
| case CMD_PDC_SET_PDR: |
| LOG_INF("C%d: Set PDR: %s", config->connector_num, |
| pdc_power_policy_to_string(port->pdr_policy)); |
| rv = pdc_set_pdr(port->pdc, port->pdr_policy); |
| break; |
| case CMD_PDC_GET_CONNECTOR_STATUS: |
| rv = pdc_get_connector_status(port->pdc, |
| &port->connector_status); |
| break; |
| case CMD_PDC_GET_CABLE_PROPERTY: |
| rv = pdc_get_cable_property(port->pdc, &port->cable_prop); |
| break; |
| case CMD_PDC_GET_VDO: |
| rv = pdc_get_vdo(port->pdc, port->vdo_req, port->vdo_type, |
| port->vdo); |
| break; |
| case CMD_PDC_GET_PD_VDO_DP_CFG_SELF: { |
| union get_vdo_t vdo_req; |
| uint8_t vdo_type; |
| |
| vdo_req.raw_value = 0; |
| vdo_req.num_vdos = 1; |
| vdo_req.vdo_origin = VDO_ORIGIN_PORT; |
| |
| vdo_type = VDO_PD_DP_CFG; |
| |
| rv = pdc_get_vdo(port->pdc, vdo_req, &vdo_type, |
| &port->vdo_dp_cfg); |
| break; |
| } |
| case CMD_PDC_GET_ATTENTION_VDO: { |
| rv = pdc_get_attention_vdo(port->pdc, &port->attention_vdo); |
| break; |
| } |
| case CMD_PDC_CONNECTOR_RESET: |
| rv = pdc_connector_reset(port->pdc, port->connector_reset); |
| break; |
| case CMD_PDC_GET_IDENTITY_DISCOVERY: |
| rv = pdc_get_identity_discovery(port->pdc, |
| &port->discovery_state); |
| break; |
| case CMD_PDC_IS_VCONN_SOURCING: |
| if (port->public_api_buff == NULL) { |
| return -EINVAL; |
| } |
| rv = pdc_is_vconn_sourcing(port->pdc, |
| (bool *)port->public_api_buff); |
| break; |
| case CMD_PDC_SET_PDOS: |
| rv = pdc_set_pdos(port->pdc, port->set_pdos.type, |
| port->set_pdos.pdos, port->set_pdos.count); |
| break; |
| case CMD_PDC_SET_FRS: |
| rv = pdc_set_frs(port->pdc, port->frs_enable); |
| break; |
| case CMD_PDC_GET_PCH_DATA_STATUS: |
| rv = pdc_get_pch_data_status(port->pdc, config->connector_num, |
| port->pch_data_status); |
| break; |
| case CMD_PDC_ACK_CC_CI: |
| rv = pdc_ack_cc_ci(port->pdc, port->ci, port->cc, |
| port->vendor_defined_ci); |
| break; |
| case CMD_PDC_GET_LPM_PPM_INFO: |
| rv = pdc_get_lpm_ppm_info(port->pdc, port->lpm_ppm_info); |
| break; |
| case CMD_PDC_GET_SBU_MUX_MODE: |
| rv = pdc_get_sbu_mux_mode(port->pdc, &port->sbu_mux_mode); |
| break; |
| case CMD_PDC_SET_SBU_MUX_MODE: |
| rv = pdc_set_sbu_mux_mode(port->pdc, port->sbu_mux_mode); |
| break; |
| case CMD_PDC_SET_AP_POWER_STATE: |
| rv = pdc_set_ap_power_state(port->pdc, |
| port->common_policy.ap_state); |
| break; |
| case CMD_PDC_SET_BBR_CTS: |
| rv = pdc_set_bbr_cts(port->pdc, port->bbr_cts_enable); |
| break; |
| case CMD_PDC_SET_BATTERY_STATUS: |
| rv = pdc_set_battery_status(port->pdc, &port->bstat); |
| break; |
| case CMD_PDC_SET_BATTERY_CAPABILITY: |
| rv = pdc_set_battery_capability(port->pdc, &port->bcap); |
| break; |
| case CMD_PDC_GET_VENDOR_STATUS: |
| rv = pdc_get_vendor_status(port->pdc, |
| &port->vendor_status_change); |
| break; |
| case CMD_PDC_GET_ALERT: |
| rv = pdc_get_alert(port->pdc, &port->ado); |
| break; |
| case CMD_PDC_SET_MAX_PDP: |
| if (pd_get_usb_pd_3a_ports() != 0) |
| rv = pdc_set_max_pdp(port->pdc, MAX_PDP_15W); |
| else |
| rv = pdc_set_max_pdp(port->pdc, MAX_PDP_7_5W); |
| break; |
| default: |
| LOG_ERR("C%d: Invalid command: %d", config->connector_num, |
| port->cmd->cmd); |
| return -EIO; |
| } |
| |
| if (rv) { |
| LOG_DBG("C%d: Unable to send command: %s rv=%d", |
| config->connector_num, pdc_cmd_names[port->cmd->cmd], |
| rv); |
| } |
| |
| return rv; |
| } |
| |
| static enum smf_state_result pdc_send_cmd_start_run(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *const config = port->dev->config; |
| int rv; |
| |
| rv = send_pdc_cmd(port); |
| if (rv) { |
| LOG_DBG("C%d: Unable to send command: %s, rv=%d", |
| config->connector_num, pdc_cmd_names[port->cmd->cmd], |
| rv); |
| } |
| |
| /* |
| * If the PDC is still processing a command (not in the IDLE state), |
| * then will remain in this state and CCI_CMD_COMPLETED can be set via |
| * the cci_event_cb function when the PDC driver finishes with the |
| * previous command, which previously didn't complete or fail within |
| * WAIT_MAX. This flag is only meaningful for the command that was just |
| * sent to the PDC. |
| */ |
| atomic_clear_bit(port->cci_flags, CCI_CMD_COMPLETED); |
| atomic_clear_bit(port->cci_flags, CCI_ERROR); |
| |
| /* Command not implemented, no need to retry, return immediately */ |
| if (rv == -ENOSYS) { |
| LOG_INF("C%d: Command (%s) not implemented", |
| config->connector_num, pdc_cmd_names[port->cmd->cmd]); |
| port->cmd->error = -ENOSYS; |
| port->cmd->pending = false; |
| set_pdc_state(port, port->send_cmd_return_state); |
| return SMF_EVENT_HANDLED; |
| } |
| /* Test if command was successful. If not, try again until max |
| * retries is reached */ |
| else if (rv) { |
| port->send_cmd.wait_counter++; |
| if (port->send_cmd.wait_counter > WAIT_MAX) { |
| /* Could not send command: TODO handle error */ |
| LOG_INF("C%d: Command (%s) retry timeout", |
| config->connector_num, |
| pdc_cmd_names[port->cmd->cmd]); |
| port->cmd->error = rv; |
| port->cmd->pending = false; |
| set_pdc_state(port, port->send_cmd_return_state); |
| } |
| return SMF_EVENT_HANDLED; |
| } |
| |
| set_pdc_state(port, PDC_SEND_CMD_WAIT); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| static void pdc_send_cmd_wait_entry(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| |
| port->send_cmd.wait_counter = 0; |
| port->send_cmd.resend_counter = 0; |
| } |
| |
| static enum smf_state_result pdc_send_cmd_wait_run(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *const config = port->dev->config; |
| |
| /* Wait for command status notification from driver */ |
| |
| /* |
| * On a PDC_RESET, the PDC initiates an initializtion and the |
| * pdc_is_init_done() function is called to check if the initialization |
| * is complete |
| */ |
| if (port->cmd->cmd == CMD_PDC_RESET) { |
| if (pdc_is_init_done(port->pdc)) { |
| port->cmd->error = 0; |
| set_pdc_state(port, port->send_cmd_return_state); |
| return SMF_EVENT_HANDLED; |
| } |
| } else if (atomic_test_and_clear_bit(port->cci_flags, CCI_BUSY)) { |
| LOG_DBG("C%d: CCI_BUSY", config->connector_num); |
| } else if (atomic_test_and_clear_bit(port->cci_flags, CCI_ERROR)) { |
| LOG_DBG("C%d: CCI_ERROR", config->connector_num); |
| /* The PDC may set both error and complete bit */ |
| atomic_clear_bit(port->cci_flags, CCI_CMD_COMPLETED); |
| |
| /* |
| * TODO(b/325114016): Use ERROR_STATUS result to adjust the |
| * number of resend attempts. If the command being sent is |
| * either a SET_UOR or SET_PDR, then should have a lower (if |
| * any) number of resend attempts. |
| */ |
| if (port->send_cmd.resend_counter < CMD_RESEND_MAX) { |
| /* Try to resend command */ |
| if (send_pdc_cmd(port) != 0) { |
| /* |
| * Set CCI_ERROR flag to trigger a resend of |
| * the pending command |
| */ |
| atomic_set_bit(port->cci_flags, CCI_ERROR); |
| } else { |
| /* PDC command resent, restart wait counter */ |
| port->send_cmd.wait_counter = 0; |
| port->send_cmd.resend_counter++; |
| } |
| } else { |
| LOG_ERR("C%d: %s resend attempts exceeded!", |
| config->connector_num, |
| pdc_cmd_names[port->cmd->cmd]); |
| port->cmd->error = -EBUSY; |
| set_pdc_state(port, port->send_cmd_return_state); |
| return SMF_EVENT_HANDLED; |
| } |
| } else if (atomic_test_and_clear_bit(port->cci_flags, |
| CCI_CMD_COMPLETED)) { |
| LOG_DBG("C%d: CCI_CMD_COMPLETED", config->connector_num); |
| |
| switch (port->cmd->cmd) { |
| case CMD_PDC_GET_CONNECTOR_STATUS: |
| handle_connector_status(port); |
| return SMF_EVENT_HANDLED; |
| case CMD_PDC_GET_ATTENTION_VDO: |
| handle_attention_vdo(port); |
| break; |
| case CMD_PDC_GET_VENDOR_STATUS: |
| handle_vendor_status(port); |
| break; |
| case CMD_PDC_GET_ALERT: |
| handle_alert(port, port->ado); |
| break; |
| default: |
| break; |
| } |
| |
| set_pdc_state(port, port->send_cmd_return_state); |
| return SMF_EVENT_HANDLED; |
| /* |
| * Note: If the command was CONNECTOR_RESET, and the type of |
| * reset was a Hard Reset, then it would also make sense to |
| * notify the host of PD_STATUS_EVENT_HARD_RESET. However, this |
| * would be redundant with the notification that will be |
| * generated later, upon completion of GET_CONNECTOR_STATUS. |
| */ |
| } else { |
| /* No response: Wait until timeout. */ |
| port->send_cmd.wait_counter++; |
| if (port->send_cmd.wait_counter > WAIT_MAX) { |
| port->cmd->error = -EBUSY; |
| if (port->cmd->cmd == CMD_PDC_GET_CONNECTOR_STATUS) { |
| /* |
| * Can't get connector status. Enter unattached |
| * state with error flag set, so it can reset |
| * the PDC. |
| */ |
| port->cmd->cmd = CMD_PDC_RESET; |
| set_pdc_state(port, PDC_UNATTACHED); |
| return SMF_EVENT_HANDLED; |
| } else { |
| set_pdc_state(port, |
| port->send_cmd_return_state); |
| return SMF_EVENT_HANDLED; |
| } |
| } |
| } |
| return SMF_EVENT_HANDLED; |
| } |
| |
| static void pdc_send_cmd_wait_exit(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| struct pdc_pdos_t *pdc_pdos; |
| |
| if (port->cmd == &port->send_cmd.public) { |
| k_event_post(&port->sm_event, PDC_PUBLIC_CMD_COMPLETE_EVENT); |
| } |
| |
| /* Completed with error. Clear complete bit */ |
| atomic_clear_bit(port->cci_flags, CCI_CMD_COMPLETED); |
| port->cmd->pending = false; |
| |
| switch (port->cmd->cmd) { |
| case CMD_PDC_GET_PDOS: |
| /* Get pointer to struct for pdos array and count */ |
| pdc_pdos = get_pdc_pdos_ptr(port, &port->get_pdo); |
| pdc_pdos->pdo_count = 0; |
| |
| /* Filter out Augmented Power Data Objects (APDO). APDOs come |
| * after the regular PDOS, so it's safe to exclude them from the |
| * pdo_count. */ |
| /* TODO This is temporary until APDOs can be handled */ |
| for (int i = 0; i < PDO_MAX_OBJECTS; i++) { |
| if ((pdc_pdos->pdos[i] & PDO_TYPE_MASK) == |
| PDO_TYPE_AUGMENTED) { |
| pdc_pdos->pdos[i] = 0; |
| } else { |
| pdc_pdos->pdo_count++; |
| } |
| } |
| break; |
| case CMD_PDC_SET_PDR: |
| case CMD_PDC_SET_UOR: |
| case CMD_PDC_SET_PDOS: |
| case CMD_PDC_SET_SINK_PATH: |
| case CMD_PDC_READ_POWER_LEVEL: |
| trigger_ppm_status_change(port); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void pdc_src_typec_only_entry(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *config = port->dev->config; |
| int port_number = config->connector_num; |
| |
| PRINT_STATE_WITH_SUBSTATE(port_number, get_pdc_state(port), |
| port->src_typec_attached_local_state, |
| src_typec_attached_local_state_names); |
| |
| set_attached_pdc_state(port, SRC_ATTACHED_TYPEC_ONLY_STATE); |
| |
| if (get_pdc_state(port) != port->send_cmd_return_state) { |
| invalidate_charger_settings(port, true); |
| port->src_typec_attached_local_state = |
| SRC_TYPEC_ATTACHED_SET_SINK_PATH_OFF; |
| |
| /* Start one shot typec only timer. This timer is used to |
| * differentiate between a port partner that supports USB PD or |
| * is typec_only. Note that the timer is not explicitly |
| * stopped. Since there is no callback associated, letting it |
| * expire in the src.attached state will have no effect and the |
| * k_timer_start call always resets the timer status. |
| */ |
| k_timer_start(&port->typec_only_timer, |
| K_USEC(TYPEC_ONLY_SINK_DEBOUNCE_TIME_US), |
| K_NO_WAIT); |
| |
| if (IS_ENABLED(CONFIG_PDC_POWER_MGMT_USB_MUX)) { |
| usb_mux_set( |
| port_number, USB_PD_MUX_USB_ENABLED, |
| USB_SWITCH_CONNECT, |
| pdc_power_mgmt_pd_get_polarity(port_number)); |
| } |
| } |
| } |
| |
| static enum smf_state_result pdc_src_typec_only_run(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *config = port->dev->config; |
| int port_number = config->connector_num; |
| |
| set_attached_pdc_state(port, SRC_ATTACHED_TYPEC_ONLY_STATE); |
| |
| /* The CCI_EVENT is set on a connector disconnect, so check the |
| * connector status and take the appropriate action. */ |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_EVENT)) { |
| queue_internal_cmd(port, CMD_PDC_GET_CONNECTOR_STATUS); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_ACK)) { |
| queue_internal_cmd(port, CMD_PDC_ACK_CC_CI); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_SET_SINK_PDOS)) { |
| LOG_INF("C%d: SRC Type-C send sink PDOs", |
| config->connector_num); |
| pdc_send_sink_pdos(port); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| switch (port->src_typec_attached_local_state) { |
| case SRC_TYPEC_ATTACHED_SET_SINK_PATH_OFF: |
| port->src_typec_attached_local_state = |
| SRC_TYPEC_ATTACHED_DEBOUNCE; |
| |
| port->sink_path_to_send = false; |
| queue_internal_cmd(port, CMD_PDC_SET_SINK_PATH); |
| return SMF_EVENT_HANDLED; |
| case SRC_TYPEC_ATTACHED_DEBOUNCE: |
| if (k_timer_status_get(&port->typec_only_timer) > 0) { |
| port->src_typec_attached_local_state = |
| SRC_TYPEC_ATTACHED_ADD_SINK; |
| } |
| return SMF_EVENT_HANDLED; |
| case SRC_TYPEC_ATTACHED_ADD_SINK: |
| port->src_typec_attached_local_state = |
| SRC_TYPEC_READ_POWER_LEVEL; |
| /* Notify DPM that a type-c only port partner is attached */ |
| pdc_dpm_add_non_pd_sink(port_number); |
| return SMF_EVENT_HANDLED; |
| case SRC_TYPEC_READ_POWER_LEVEL: |
| port->src_typec_attached_local_state = SRC_TYPEC_ATTACHED_RUN; |
| queue_internal_cmd(port, CMD_PDC_READ_POWER_LEVEL); |
| break; |
| case SRC_TYPEC_ATTACHED_RUN: |
| run_typec_src_policies(port); |
| break; |
| case SRC_TYPEC_ATTACHED_INVALID: |
| __builtin_unreachable(); |
| } |
| return SMF_EVENT_HANDLED; |
| } |
| |
| static void pdc_snk_typec_only_entry(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *config = port->dev->config; |
| int port_number = config->connector_num; |
| |
| port->send_cmd.intern.pending = false; |
| if (get_pdc_state(port) != port->send_cmd_return_state) { |
| port->snk_typec_attached_local_state = |
| SNK_TYPEC_ATTACHED_DEBOUNCE; |
| |
| /* Start one shot typec only timer. This timer is used to |
| * differentiate between a port partner that supports USB PD or |
| * is typec_only. Note that the timer is not explicitly |
| * stopped. Since there is no callback associated, letting it |
| * expire in the snk.attached state will have no effect and the |
| * k_timer_start call always resets the timer status. |
| */ |
| k_timer_start(&port->typec_only_timer, |
| K_USEC(PD_T_SINK_WAIT_CAP), K_NO_WAIT); |
| |
| if (IS_ENABLED(CONFIG_PDC_POWER_MGMT_USB_MUX)) { |
| usb_mux_set( |
| port_number, USB_PD_MUX_USB_ENABLED, |
| USB_SWITCH_CONNECT, |
| pdc_power_mgmt_pd_get_polarity(port_number)); |
| } |
| } |
| |
| PRINT_STATE_WITH_SUBSTATE(port_number, get_pdc_state(port), |
| port->snk_typec_attached_local_state, |
| snk_typec_attached_local_state_names); |
| |
| set_attached_pdc_state(port, SNK_ATTACHED_TYPEC_ONLY_STATE); |
| } |
| |
| static enum smf_state_result pdc_snk_typec_only_run(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *const config = port->dev->config; |
| |
| set_attached_pdc_state(port, SNK_ATTACHED_TYPEC_ONLY_STATE); |
| |
| /* The CCI_EVENT is set to re-query connector status, so check the |
| * connector status and take the appropriate action. |
| */ |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_EVENT)) { |
| queue_internal_cmd(port, CMD_PDC_GET_CONNECTOR_STATUS); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_ACK)) { |
| queue_internal_cmd(port, CMD_PDC_ACK_CC_CI); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| if (atomic_test_and_clear_bit(port->cci_flags, CCI_SET_SINK_PDOS)) { |
| LOG_INF("C%d: SNK Type-C send sink PDOs", |
| config->connector_num); |
| pdc_send_sink_pdos(port); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| switch (port->snk_typec_attached_local_state) { |
| case SNK_TYPEC_ATTACHED_DEBOUNCE: |
| if (k_timer_status_get(&port->typec_only_timer) > 0) { |
| port->snk_typec_attached_local_state = |
| SNK_TYPEC_ATTACHED_SET_CHARGE_CURRENT; |
| } |
| return SMF_EVENT_HANDLED; |
| case SNK_TYPEC_ATTACHED_SET_CHARGE_CURRENT: |
| port->snk_typec_attached_local_state = |
| SNK_TYPEC_READ_POWER_LEVEL; |
| |
| /* Once we're updating the charger with the new current limit, |
| * it's safe to clear the policy bit. If the PDC reports |
| * a new change to power operation mode, this but will be |
| * set again. |
| */ |
| atomic_clear_bit(port->snk_policy.flags, |
| SNK_POLICY_UPDATE_TYPEC_CURRENT); |
| |
| if (port->sink_path_status) { |
| charge_manager_set_supplier(config->connector_num, |
| CHARGE_SUPPLIER_TYPEC); |
| } |
| |
| pd_set_input_current_limit(config->connector_num, 0, 0); |
| typec_set_input_current_limit(config->connector_num, |
| port->typec_current_ma, 5000); |
| |
| charge_manager_update_dualrole(config->connector_num, |
| CAP_DEDICATED); |
| break; |
| case SNK_TYPEC_READ_POWER_LEVEL: |
| port->snk_typec_attached_local_state = SNK_TYPEC_ATTACHED_RUN; |
| queue_internal_cmd(port, CMD_PDC_READ_POWER_LEVEL); |
| break; |
| case SNK_TYPEC_ATTACHED_RUN: |
| run_typec_snk_policies(port); |
| break; |
| case SNK_TYPEC_ATTACHED_INVALID: |
| __builtin_unreachable(); |
| } |
| return SMF_EVENT_HANDLED; |
| } |
| |
| static void pdc_init_entry(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *config = port->dev->config; |
| int port_number = config->connector_num; |
| |
| PRINT_STATE_WITH_SUBSTATE(port_number, get_pdc_state(port), |
| port->init_local_state, |
| init_local_state_names); |
| |
| if (get_pdc_state(port) != port->send_cmd_return_state) { |
| /* True if entering the init state for the first time, as |
| * opposed to returning from a completed command called by one |
| * of the local states (sub-steps) |
| */ |
| |
| /* Initialize Send Command data */ |
| send_cmd_init(port); |
| /* Set up GET_VDO command data */ |
| discovery_info_init(port); |
| |
| if (IS_ENABLED(CONFIG_PDC_POWER_MGMT_USB_MUX)) { |
| usb_mux_init(port_number); |
| } |
| port->init_local_state = INIT_WAIT_FOR_READY; |
| port->public_api_buff = NULL; |
| } |
| } |
| |
| static void pdc_notify_ap_power_state(int port, enum power_state state) |
| { |
| pdc_data[port]->port.common_policy.ap_state = state; |
| atomic_set_bit(pdc_data[port]->port.common_policy.flags, |
| COMMON_POLICY_SET_POWER_STATE); |
| LOG_INF("C%d: Setting AP power state to %s", port, |
| state == POWER_S0 ? "S0" : "S5"); |
| } |
| |
| /** |
| * @brief Chipset Resume (S3->S0) Policy 1: Set a flag to perform a one-time |
| * test if we should swap to a source role. (applicable only if we are |
| * currently a sink) |
| */ |
| static void enforce_pd_chipset_resume_policy_1(int port) |
| { |
| LOG_DBG("C%d: Chipset Resume Policy 1", port); |
| |
| /* If we're in a sink role, run a check to determine if we'd prefer a |
| * source role. |
| */ |
| atomic_set_bit(pdc_data[port]->port.snk_policy.flags, |
| SNK_POLICY_EVAL_SWAP_TO_SRC); |
| } |
| |
| /* |
| * PD policy handlers |
| * |
| * These functions are triggered by AP power state changes via hooks and also |
| * through the PDC power management state machine's init state in cases when a |
| * late system jump happened. |
| * |
| * These functions should set flags to trigger actions from within the state |
| * machine, rather than performing operations directly. |
| */ |
| |
| /** |
| * @brief Chipset Resume (S3->S0) Policy 2: |
| * a) Set DRP role based on platform |
| */ |
| static void enforce_pd_chipset_resume_policy_2(int port) |
| { |
| LOG_DBG("C%d: Chipset Resume Policy 2", port); |
| |
| pdc_power_mgmt_set_dual_role(port, pd_get_drp_state_in_s0()); |
| } |
| |
| /** |
| * @brief Chipset Suspend (S0->S3) Policy 1: |
| * a) DRP TOGGLE OFF |
| */ |
| static void enforce_pd_chipset_suspend_policy_1(int port) |
| { |
| LOG_DBG("C%d: Chipset Suspend Policy 1", port); |
| |
| atomic_clear_bit(pdc_data[port]->port.snk_policy.flags, |
| SNK_POLICY_EVAL_SWAP_TO_SRC); |
| pdc_power_mgmt_set_dual_role(port, PD_DRP_TOGGLE_OFF); |
| } |
| |
| /** |
| * Chipset Shutdown (S3->S5) Policy 1: |
| * a) DRP Force SINK |
| */ |
| static void enforce_pd_chipset_shutdown_policy_1(int port) |
| { |
| LOG_DBG("C%d: Chipset Shutdown Policy 1", port); |
| |
| pdc_power_mgmt_set_dual_role(port, PD_DRP_FORCE_SINK); |
| /* Notify PDC that AP is shutting down to disable retimer */ |
| pdc_notify_ap_power_state(port, POWER_S5); |
| } |
| |
| static void set_hpd_wake_watch(int port); |
| |
| static void clear_hpd_wake_watch(int port); |
| |
| /** |
| * @brief Apply correct policy based on system power state |
| * |
| * This is normally triggered by hooks on AP power state changes |
| * (HOOK_CHIPSET_RESUME, etc) elsewhere in this file. The hooks enforce |
| * hysteresis on the power state to avoid rapid policy flapping. |
| * |
| * In the case of a late sysjump, this function is also called during |
| * init to force the correct policy, since the normal start-up power |
| * state transition hooks will not be occur. |
| * |
| * Note: this should run once, and not per-port. |
| */ |
| static void pdc_apply_power_state_policy(struct k_work *work) |
| { |
| uint8_t port_count = pdc_power_mgmt_get_usb_pd_port_count(); |
| |
| if (chipset_in_state(CHIPSET_STATE_ON)) { |
| LOG_INF("PD: AP is ON: apply 'resume' policy"); |
| for (int i = 0; i < port_count; i++) { |
| /* Notify PDC that the AP is in S0 to enable retimers */ |
| pdc_notify_ap_power_state(i, POWER_S0); |
| /* |
| * Setting the dual role state clears the policy flag |
| * SNK_POLICY_SWAP_TO_SRC which may get set in |
| * enforce_pd_chipset_resume_policy_1() so this policy |
| * function needs to be called after resume_policy_2() |
| * which sets DRP mode on. |
| */ |
| enforce_pd_chipset_resume_policy_2(i); |
| enforce_pd_chipset_resume_policy_1(i); |
| clear_hpd_wake_watch(i); |
| } |
| } else if (chipset_in_state(CHIPSET_STATE_ANY_SUSPEND)) { |
| LOG_INF("PD: AP is SUSPENDED: apply 'suspend' policy"); |
| for (int i = 0; i < port_count; i++) { |
| enforce_pd_chipset_suspend_policy_1(i); |
| set_hpd_wake_watch(i); |
| } |
| } else if (chipset_in_state(CHIPSET_STATE_ANY_OFF)) { |
| LOG_INF("PD: AP is OFF: apply 'shutdown' policy"); |
| for (int i = 0; i < port_count; i++) { |
| enforce_pd_chipset_shutdown_policy_1(i); |
| } |
| } |
| } |
| |
| static K_WORK_DELAYABLE_DEFINE(pdc_apply_power_state_policy_work, |
| pdc_apply_power_state_policy); |
| |
| /** |
| * @brief Returns true if all PDC port drivers have finished initializing |
| * |
| * @return bool True if all ports are ready, false if still pending. |
| */ |
| static bool pdc_all_ports_ready(void) |
| { |
| int num_ports = pdc_power_mgmt_get_usb_pd_port_count(); |
| |
| for (uint8_t i = 0; i < num_ports; i++) { |
| if (!pdc_is_init_done(pdc_data[i]->port.pdc)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static enum smf_state_result pdc_init_run(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *const config = port->dev->config; |
| |
| switch (port->init_local_state) { |
| case INIT_WAIT_FOR_READY: |
| if (!pdc_is_init_done(port->pdc)) { |
| /* Still waiting on driver to be ready */ |
| return SMF_EVENT_HANDLED; |
| } |
| |
| LOG_INF("C%d: PDC Subsystem Started", config->connector_num); |
| |
| /* Apply policy in case of a late sysjump since we won't receive |
| * the usual hook calls upon AP power state changes. Only called |
| * once, after all port drivers are ready. |
| */ |
| if (system_jumped_late() && pdc_all_ports_ready()) { |
| LOG_INF("PD: Handling late sysjump"); |
| pdc_apply_power_state_policy( |
| &pdc_apply_power_state_policy_work.work); |
| } |
| |
| port->init_local_state = INIT_SET_SBU_MUX_FORCED_DEBUG; |
| |
| /* Proceed directly to next sub-state */ |
| __fallthrough; |
| |
| case INIT_SET_SBU_MUX_FORCED_DEBUG: |
| port->init_local_state = INIT_SET_SINK_PDOS; |
| |
| if (atomic_test_and_clear_bit( |
| port->common_policy.flags, |
| COMMON_POLICY_SET_SBU_MUX_TO_FORCED_DEBUG)) { |
| /* Set the SBU mux operating mode to forced-debug */ |
| port->sbu_mux_mode = PDC_SBU_MUX_MODE_FORCE_DBG; |
| queue_internal_cmd(port, CMD_PDC_SET_SBU_MUX_MODE); |
| break; |
| } |
| |
| /* If flag is unset, proceed directly to next sub-state */ |
| __fallthrough; |
| |
| case INIT_SET_SINK_PDOS: |
| port->init_local_state = INIT_SET_SRC_PDOS; |
| pdc_send_sink_pdos(port); |
| queue_internal_cmd(port, CMD_PDC_SET_PDOS); |
| break; |
| |
| case INIT_SET_SRC_PDOS: |
| port->init_local_state = INIT_SET_FRS; |
| port->attached_state = INIT_STATE; |
| |
| pdc_power_mgmt_set_current_limit( |
| config->connector_num, |
| pdc_power_mgmt_get_default_current_limit( |
| config->connector_num)); |
| |
| port->set_pdos = (struct set_pdos_t){ |
| .count = 1, |
| .type = SOURCE_PDO, |
| .pdos = { port->src_policy.lpm_src_pdo }, |
| }; |
| queue_internal_cmd(port, CMD_PDC_SET_PDOS); |
| break; |
| case INIT_SET_FRS: |
| port->init_local_state = INIT_SET_MAX_PDP; |
| if (pdc_power_mgmt_get_frs_hw_supported( |
| config->connector_num)) { |
| port->frs_enable = true; |
| } else { |
| port->frs_enable = false; |
| } |
| queue_internal_cmd(port, CMD_PDC_SET_FRS); |
| break; |
| case INIT_SET_MAX_PDP: |
| port->init_local_state = INIT_GET_CONNECTOR_STATUS; |
| queue_internal_cmd(port, CMD_PDC_SET_MAX_PDP); |
| break; |
| case INIT_GET_CONNECTOR_STATUS: |
| /* Send the connector status command to determine which state to |
| * enter |
| */ |
| queue_internal_cmd(port, CMD_PDC_GET_CONNECTOR_STATUS); |
| break; |
| case INIT_INVALID: |
| __builtin_unreachable(); |
| } |
| return SMF_EVENT_HANDLED; |
| } |
| |
| static void pdc_suspended_entry(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| const struct pdc_config_t *const config = port->dev->config; |
| |
| PRINT_STATE(config->connector_num, get_pdc_state(port)); |
| } |
| |
| static enum smf_state_result pdc_suspended_run(void *obj) |
| { |
| struct pdc_port_t *port = (struct pdc_port_t *)obj; |
| |
| if (atomic_get(&port->suspend)) { |
| /* Still suspended. Do nothing. */ |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /* No longer suspended. Do a full reset. */ |
| init_port_variables(port, true); |
| set_pdc_state(port, PDC_INIT); |
| return SMF_EVENT_HANDLED; |
| } |
| |
| /** |
| * @brief Populate state table |
| */ |
| static const struct smf_state pdc_states[] = { |
| /* Normal States */ |
| [PDC_INIT] = SMF_CREATE_STATE(pdc_init_entry, pdc_init_run, NULL, NULL, |
| NULL), |
| [PDC_UNATTACHED] = SMF_CREATE_STATE( |
| pdc_unattached_entry, pdc_unattached_run, NULL, NULL, NULL), |
| [PDC_SNK_ATTACHED] = SMF_CREATE_STATE( |
| pdc_snk_attached_entry, pdc_snk_attached_run, NULL, NULL, NULL), |
| [PDC_SRC_ATTACHED] = SMF_CREATE_STATE( |
| pdc_src_attached_entry, pdc_src_attached_run, NULL, NULL, NULL), |
| [PDC_SEND_CMD_START] = SMF_CREATE_STATE(pdc_send_cmd_start_entry, |
| pdc_send_cmd_start_run, NULL, |
| NULL, NULL), |
| [PDC_SEND_CMD_WAIT] = |
| SMF_CREATE_STATE(pdc_send_cmd_wait_entry, pdc_send_cmd_wait_run, |
| pdc_send_cmd_wait_exit, NULL, NULL), |
| [PDC_SRC_TYPEC_ONLY] = SMF_CREATE_STATE(pdc_src_typec_only_entry, |
| pdc_src_typec_only_run, NULL, |
| NULL, NULL), |
| [PDC_SNK_TYPEC_ONLY] = SMF_CREATE_STATE(pdc_snk_typec_only_entry, |
| pdc_snk_typec_only_run, NULL, |
| NULL, NULL), |
| [PDC_SUSPENDED] = SMF_CREATE_STATE(pdc_suspended_entry, |
| pdc_suspended_run, NULL, NULL, NULL), |
| [PDC_DISABLED] = SMF_CREATE_STATE(NULL, NULL, NULL, NULL, NULL), |
| }; |
| |
| /** |
| * Do not use for internal checks since port counts are not known until all |
| * pdc_power_mgmt devices initialize. |
| */ |
| bool pdc_power_mgmt_is_pdc_port_valid(int port) |
| { |
| return (port >= 0) && (port < pdc_power_mgmt_get_usb_pd_port_count()) && |
| (get_pdc_state(&pdc_data[port]->port) != PDC_DISABLED); |
| } |
| |
| /** |
| * @brief CCI event handler call back |
| */ |
| static void pdc_cc_handler_cb(const struct device *dev, |
| const struct pdc_callback *callback, |
| union cci_event_t cci_event) |
| { |
| struct pdc_port_t *port = |
| CONTAINER_OF(callback, struct pdc_port_t, cc_cb); |
| bool post_event = false; |
| |
| /* Handle busy event from driver */ |
| if (cci_event.busy) { |
| atomic_set_bit(port->cci_flags, CCI_BUSY); |
| post_event = true; |
| } |
| |
| /* Handle error event from driver */ |
| if (cci_event.error) { |
| atomic_set_bit(port->cci_flags, CCI_ERROR); |
| post_event = true; |
| } |
| |
| /* Handle command completed event from driver */ |
| if (cci_event.command_completed) { |
| atomic_set_bit(port->cci_flags, CCI_CMD_COMPLETED); |
| post_event = true; |
| } |
| |
| if (post_event) |
| k_event_post(&port->sm_event, PDC_SM_EVENT); |
| } |
| |
| static void pdc_ci_handler_cb(const struct device *dev, |
| const struct pdc_callback *callback, |
| union cci_event_t cci_event) |
| { |
| struct pdc_port_t *port = |
| CONTAINER_OF(callback, struct pdc_port_t, ci_cb); |
| const struct pdc_config_t *const config = port->dev->config; |
| bool post_event = false; |
| |
| /* Handle generic vendor defined event from driver */ |
| if (cci_event.vendor_defined_indicator) { |
| atomic_set_bit(port->cci_flags, CCI_EVENT); |
| atomic_set_bit(port->cci_flags, CCI_VENDOR_EVENT); |
| post_event = true; |
| } |
| |
| if (cci_event.connector_change == config->connector_num + 1) { |
| /* Clear the status ready event before setting the PPM event |
| * bit. This will correctly order calls to grab the cached |
| * connector status in |
| * |pdc_power_mgmt_get_connector_status_for_ppm|. |
| */ |
| k_event_clear(&port->settle_event, |
| PDC_PPM_CONNECTOR_STATUS_READY); |
| atomic_set_bit(port->cci_flags, CCI_PPM_EVENT); |
| post_event = true; |
| } |
| |
| if (post_event) |
| k_event_post(&port->sm_event, PDC_SM_EVENT); |
| } |
| |
| static void init_port_variables(struct pdc_port_t *port, |
| bool reset_charge_manager) |
| { |
| /* This optionally seeds the Charge Manager */ |
| invalidate_charger_settings(port, reset_charge_manager); |
| |
| /* Init port variables */ |
| |
| atomic_clear(port->pdc_cmd_flags); |
| atomic_clear(port->cci_flags); |
| port->port_event = ATOMIC_INIT(0); |
| port->get_pdo.updating = false; |
| |
| port->last_state = PDC_INIT; |
| port->next_state = PDC_INIT; |
| port->send_cmd_return_state = PDC_INVALID; |
| discovery_info_init(port); |
| } |
| |
| /** |
| * @brief Initialize the PDC Subsystem |
| */ |
| static int pdc_subsys_init(const struct device *dev) |
| { |
| struct pdc_data_t *data = dev->data; |
| struct pdc_port_t *port = &data->port; |
| const struct pdc_config_t *const config = dev->config; |
| int rv; |
| |
| const struct device *pdc = |
| pdc_power_mgmt_get_port_pdc_driver(config->connector_num); |
| |
| #ifdef CONFIG_PDC_RUNTIME_PORT_CONFIG |
| /* Runtime-defined PDC configuration. Need to initialize this PDC |
| * driver here manually. */ |
| |
| uint8_t active_port_count = pdc_power_mgmt_get_usb_pd_port_count(); |
| |
| if (config->connector_num >= active_port_count || pdc == NULL) { |
| LOG_ERR("C%d: Port disabled per board configuration " |
| "(total active ports: %u)", |
| config->connector_num, active_port_count); |
| |
| goto disable_port; |
| } |
| |
| /* Try initializing this driver */ |
| rv = device_init(pdc); |
| if (rv) { |
| LOG_ERR("C%d: Cannot initialize PDC: %d (PDC %p %s). " |
| "Disabling this port", |
| config->connector_num, rv, pdc, |
| pdc->name ? pdc->name : "<no name>"); |
| goto disable_port; |
| } |
| #else /* !defined(CONFIG_PDC_RUNTIME_PORT_CONFIG) */ |
| /* Static PDC configuration. Make sure the assigned PDC is ready. */ |
| |
| if (!device_is_ready(pdc)) { |
| LOG_ERR_DEVICE_NOT_READY(pdc); |
| goto disable_port; |
| } |
| #endif /* !defined(CONFIG_PDC_RUNTIME_PORT_CONFIG) */ |
| |
| port->pdc = pdc; |
| |
| init_port_variables(port, false); |
| port->drp = port->una_policy.drp_mode; |
| |
| /* Set cc call back */ |
| port->cc_cb.handler = pdc_cc_handler_cb; |
| pdc_set_cc_callback(port->pdc, &port->cc_cb); |
| |
| /* Set ci call back */ |
| port->ci_cb.handler = pdc_ci_handler_cb; |
| rv = pdc_add_ci_callback(port->pdc, &port->ci_cb); |
| if (rv) |
| LOG_ERR("C%d: Failed to add CI callback (%d)", |
| config->connector_num, rv); |
| |
| /* Initialize state machine run event */ |
| k_event_init(&port->sm_event); |
| k_event_init(&port->settle_event); |
| |
| /* Initialize command mutex */ |
| k_mutex_init(&port->mtx); |
| smf_set_initial(&port->ctx, &pdc_states[PDC_INIT]); |
| |
| /* Initialize typec only timer */ |
| k_timer_init(&port->typec_only_timer, NULL, NULL); |
| |
| /* Initialize platform policy */ |
| enforce_pd_chipset_shutdown_policy_1(config->connector_num); |
| |
| /* Create the thread for this port */ |
| config->create_thread(dev); |
| |
| return 0; |
| |
| disable_port: |
| /* Prevent sending public API commands. Note: we never create a |
| * driver thread in this code path, so nothing will happen for |
| * this port. */ |
| smf_set_initial(&port->ctx, &pdc_states[PDC_DISABLED]); |
| |
| return -ENODEV; |
| } |
| |
| /** |
| * @brief Start the PDC subsystem and the PDC driver threads |
| */ |
| void pdc_subsys_start(void) |
| { |
| for (int port = 0; port < ARRAY_SIZE(pdc_data); port++) { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| /* Skip over inactive ports */ |
| continue; |
| } |
| |
| /* Start the PDC driver threads */ |
| if (device_is_ready(pdc_data[port]->port.pdc)) { |
| pdc_start_thread(pdc_data[port]->port.pdc); |
| } |
| |
| /* Start the PDC power management threads */ |
| if (device_is_ready(pdc_data[port]->port.dev)) { |
| k_thread_start(pdc_data[port]->thread); |
| } |
| } |
| } |
| |
| /** |
| * @brief Returns true if command can be executed without a port partner |
| * connection |
| */ |
| static bool is_connectionless_cmd(enum pdc_cmd_t pdc_cmd) |
| { |
| switch (pdc_cmd) { |
| case CMD_PDC_RESET: |
| __fallthrough; |
| case CMD_PDC_SET_POWER_LEVEL: |
| __fallthrough; |
| case CMD_PDC_GET_INFO: |
| __fallthrough; |
| case CMD_PDC_GET_PCH_DATA_STATUS: |
| __fallthrough; |
| case CMD_PDC_SET_DRP: |
| __fallthrough; |
| case CMD_PDC_GET_DRP: |
| __fallthrough; |
| case CMD_PDC_GET_LPM_PPM_INFO: |
| __fallthrough; |
| case CMD_PDC_GET_SBU_MUX_MODE: |
| __fallthrough; |
| case CMD_PDC_SET_BBR_CTS: |
| __fallthrough; |
| case CMD_PDC_SET_SBU_MUX_MODE: |
| __fallthrough; |
| case CMD_PDC_SET_FRS: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * @brief Called from a public API function to block until the command completes |
| * or time outs |
| */ |
| static int public_api_block(int port, enum pdc_cmd_t pdc_cmd) |
| { |
| int ret; |
| struct cmd_t *public_cmd; |
| k_timepoint_t cmd_timepoint; |
| |
| ret = queue_public_cmd(&pdc_data[port]->port, pdc_cmd); |
| if (ret) { |
| LOG_ERR("C%d: Could not queue %s: %d", port, |
| pdc_cmd_names[pdc_cmd], ret); |
| return ret; |
| } |
| |
| public_cmd = &pdc_data[port]->port.send_cmd.public; |
| |
| /* TODO: Investigate using a semaphore here instead of while loop */ |
| /* Block calling thread until command is processed, errors or timeout |
| * occurs. */ |
| cmd_timepoint = sys_timepoint_calc(K_MSEC(PDC_CMD_TIMEOUT_MS)); |
| |
| while (public_cmd->pending && !public_cmd->error) { |
| /* block until command completes or max block count is reached |
| */ |
| |
| /* Wait for timeout or event */ |
| ret = k_event_wait(&pdc_data[port]->port.sm_event, |
| PDC_PUBLIC_CMD_COMPLETE_EVENT, false, |
| K_MSEC(PUBLIC_CMD_DELAY_MS)); |
| |
| if (ret != 0) { |
| k_event_clear(&pdc_data[port]->port.sm_event, |
| PDC_PUBLIC_CMD_COMPLETE_EVENT); |
| } |
| |
| if (sys_timepoint_expired(cmd_timepoint)) { |
| /* something went wrong */ |
| LOG_ERR("C%d: Public API blocking timeout: %s", port, |
| pdc_cmd_names[public_cmd->cmd]); |
| public_cmd->pending = false; |
| return -EBUSY; |
| } |
| |
| /* Check for commands that don't require a connection */ |
| if (is_connectionless_cmd(public_cmd->cmd)) { |
| continue; |
| } |
| |
| /* The system is blocking on a command that requires a |
| * connection, so return if disconnected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| LOG_ERR("C%d: Command %s requires connection", port, |
| pdc_cmd_names[public_cmd->cmd]); |
| return -EIO; |
| } |
| } |
| |
| if (public_cmd->error) { |
| LOG_ERR("C%d: Public API command %s not sent, err=%d", port, |
| pdc_cmd_names[public_cmd->cmd], public_cmd->error); |
| return public_cmd->error; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * PDC Power Management Public API |
| */ |
| static bool pdc_power_mgmt_is_sink_connected(int port) |
| { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return false; |
| } |
| |
| return pdc_data[port]->port.attached_state == SNK_ATTACHED_STATE; |
| } |
| |
| static bool pdc_power_mgmt_is_source_connected(int port) |
| { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return false; |
| } |
| |
| return pdc_data[port]->port.attached_state == SRC_ATTACHED_STATE; |
| } |
| |
| test_mockable bool pdc_power_mgmt_is_connected(int port) |
| { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return false; |
| } |
| |
| return pdc_data[port]->port.attached_state != UNATTACHED_STATE; |
| } |
| |
| #ifndef CONFIG_PDC_RUNTIME_PORT_CONFIG |
| /* |
| * When static port config is used (normal), each named-usbc-port must have |
| * only one PDC phandle entry, and it must not be marked zephyr,deferred-init. |
| */ |
| #define CHECK_NAMED_USBC_PORT(inst) \ |
| BUILD_ASSERT(1 == DT_INST_PROP_LEN(inst, pdc)); \ |
| BUILD_ASSERT(0 == DT_PROP(DT_INST_PROP_BY_IDX(inst, pdc, 0), \ |
| zephyr_deferred_init)); |
| |
| #ifndef CONFIG_ZTEST |
| /* Only enforce this on real builds so tests can try some failure scenarios */ |
| DT_INST_FOREACH_STATUS_OKAY(CHECK_NAMED_USBC_PORT) |
| #endif /* !defined(CONFIG_ZTEST) */ |
| |
| /* Find the associated PDC driver using static devicetree information */ |
| const struct device *pdc_power_mgmt_get_port_pdc_driver(int port) |
| { |
| if (port < 0 || port >= CONFIG_USB_PD_PORT_MAX_COUNT) { |
| return NULL; |
| } |
| |
| /* This array is statically initialized and should always have the |
| * correct data */ |
| return pdc_data[port]->port.pdc; |
| } |
| |
| /* Note: this is defined in pdc_runtime_port_config.c when |
| * CONFIG_PDC_RUNTIME_PORT_CONFIG is enabled */ |
| uint8_t pdc_power_mgmt_get_usb_pd_port_count(void) |
| { |
| return CONFIG_USB_PD_PORT_MAX_COUNT; |
| } |
| #endif /* !defined(CONFIG_PDC_RUNTIME_PORT_CONFIG) */ |
| |
| int pdc_power_mgmt_set_active_charge_port(int charge_port) |
| { |
| /* Note: pdc_power_mgmt_set_active_charge_port() does not alter the |
| * active charging port. It triggers all ports to ask charge_manager |
| * for the currently active port and adjust their sink path states. |
| * |
| * Overriding the active charge port externally should be done through |
| * charge manager's charge_manager_set_override() function. |
| */ |
| |
| LOG_INF("%s: charge_port=%d", __func__, charge_port); |
| |
| /* Contact all ports by raising a policy flag. The individual port |
| * state machines will react by checking if they are now the active |
| * charge port and adjust their sink paths accordingly. |
| */ |
| |
| for (int i = 0; i < pdc_power_mgmt_get_usb_pd_port_count(); i++) { |
| atomic_set_bit(pdc_data[i]->port.snk_policy.flags, |
| SNK_POLICY_SET_ACTIVE_CHARGE_PORT); |
| k_event_post(&pdc_data[i]->port.sm_event, PDC_SM_EVENT); |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| void pdc_power_mgmt_check_pr_swap_needed(int port) |
| { |
| atomic_set_bit(pdc_data[port]->port.snk_policy.flags, |
| SNK_POLICY_EVAL_SWAP_TO_SRC); |
| } |
| |
| int pdc_power_mgmt_set_new_power_request(int port) |
| { |
| /* Make sure port is sink connected */ |
| if (!pdc_power_mgmt_is_sink_connected(port)) { |
| return -ENOTCONN; |
| } |
| |
| LOG_INF("C%d: New power request", port); |
| |
| atomic_set_bit(pdc_data[port]->port.snk_policy.flags, |
| SNK_POLICY_NEW_POWER_REQUEST); |
| |
| k_event_post(&pdc_data[port]->port.sm_event, PDC_SM_EVENT); |
| |
| return EC_SUCCESS; |
| } |
| |
| uint8_t pdc_power_mgmt_get_task_state(int port) |
| { |
| enum pdc_state_t indicated_state, actual_state; |
| |
| if (port < 0 || port >= CONFIG_USB_PD_PORT_MAX_COUNT) { |
| /* Ports outside of this range are never valid and would exceed |
| * the bounds of `pdc_data`. */ |
| return PDC_INVALID; |
| } |
| |
| actual_state = get_pdc_state(&pdc_data[port]->port); |
| |
| switch (actual_state) { |
| case PDC_SEND_CMD_START: |
| case PDC_SEND_CMD_WAIT: |
| indicated_state = pdc_data[port]->port.send_cmd_return_state; |
| break; |
| default: |
| indicated_state = actual_state; |
| } |
| |
| return indicated_state; |
| } |
| |
| int pdc_power_mgmt_comm_is_enabled(int port) |
| { |
| if (pdc_power_mgmt_is_sink_connected(port) || |
| pdc_power_mgmt_is_source_connected(port)) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| test_mockable bool pdc_power_mgmt_get_vconn_state(int port) |
| { |
| bool vconn_sourcing; |
| |
| /* Make sure port is source connected */ |
| if (!pdc_power_mgmt_is_source_connected(port)) { |
| return false; |
| } |
| |
| pdc_data[port]->port.public_api_buff = (uint8_t *)&vconn_sourcing; |
| |
| /* Block until command completes */ |
| if (public_api_block(port, CMD_PDC_IS_VCONN_SOURCING)) { |
| /* something went wrong */ |
| pdc_data[port]->port.public_api_buff = NULL; |
| return false; |
| } |
| |
| pdc_data[port]->port.public_api_buff = NULL; |
| |
| return vconn_sourcing; |
| } |
| |
| bool pdc_power_mgmt_get_partner_usb_comm_capable(int port) |
| { |
| /* Make sure port is connected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return false; |
| } |
| |
| return pdc_data[port]->port.ccaps.op_mode_usb2 | |
| pdc_data[port]->port.ccaps.op_mode_usb3 | |
| pdc_data[port]->port.ccaps.ext_op_mode_usb4_gen2 | |
| pdc_data[port]->port.ccaps.ext_op_mode_usb4_gen3 | |
| pdc_data[port]->port.ccaps.ext_op_mode_usb4_gen4; |
| } |
| |
| bool pdc_power_mgmt_get_partner_unconstr_power(int port) |
| { |
| /* Make sure port is sink connected */ |
| if (!pdc_power_mgmt_is_sink_connected(port)) { |
| return false; |
| } |
| |
| /* Only the fixed 5V PDO at index 0 has the UP and DRP bits set */ |
| uint32_t vsafe_5v_pdo = |
| pdc_data[port]->port.snk_policy.partner_src_pdos.pdos[0]; |
| |
| return (vsafe_5v_pdo & PDO_FIXED_GET_UNCONSTRAINED_PWR); |
| } |
| |
| static int pdc_power_mgmt_request_data_swap_intern(int port, |
| enum pd_data_role role) |
| { |
| /* Make sure port is connected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return 1; |
| } |
| |
| if (role == PD_ROLE_UFP) { |
| /* Attempt to swap to UFP */ |
| pdc_data[port]->port.uor = (union uor_t){ |
| .swap_to_dfp = 0, |
| .swap_to_ufp = 1, |
| /* Allow external swaps back to DFP */ |
| .accept_dr_swap = 1, |
| }; |
| } else if (role == PD_ROLE_DFP) { |
| /* Attempt to swap to DFP */ |
| pdc_data[port]->port.uor = (union uor_t){ |
| .swap_to_dfp = 1, |
| .swap_to_ufp = 0, |
| /* Prefer staying in DFP */ |
| .accept_dr_swap = 0, |
| }; |
| } else { |
| return EC_SUCCESS; |
| } |
| |
| /* Block until command completes */ |
| if (public_api_block(port, CMD_PDC_SET_UOR)) { |
| /* something went wrong */ |
| return 1; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| test_mockable void pdc_power_mgmt_request_data_swap(int port) |
| { |
| enum pd_data_role data_role = pdc_power_mgmt_pd_get_data_role(port); |
| |
| switch (data_role) { |
| case PD_ROLE_DFP: |
| LOG_INF("C%d: Attempt data role swap from DFP to UFP", port); |
| pdc_power_mgmt_request_data_swap_intern(port, PD_ROLE_UFP); |
| break; |
| case PD_ROLE_UFP: |
| LOG_INF("C%d: Attempt data role swap from UFP to DFP", port); |
| pdc_power_mgmt_request_data_swap_intern(port, PD_ROLE_DFP); |
| break; |
| default: |
| LOG_ERR("C%d: Cannot data role swap: port is disconnected or invalid", |
| port); |
| } |
| } |
| |
| test_mockable void pdc_power_mgmt_request_power_swap(int port) |
| { |
| if (pdc_power_mgmt_is_sink_connected(port)) { |
| LOG_INF("C%d: Request power role swap to source", port); |
| |
| atomic_set_bit(pdc_data[port]->port.snk_policy.flags, |
| SNK_POLICY_SWAP_TO_SRC); |
| k_event_post(&pdc_data[port]->port.sm_event, PDC_SM_EVENT); |
| |
| } else if (pdc_power_mgmt_is_source_connected(port)) { |
| LOG_INF("C%d: Request power role swap to sink", port); |
| |
| atomic_set_bit(pdc_data[port]->port.src_policy.flags, |
| SRC_POLICY_SWAP_TO_SNK); |
| k_event_post(&pdc_data[port]->port.sm_event, PDC_SM_EVENT); |
| } else { |
| LOG_ERR("C%d: Cannot swap power role: " |
| "port not PD-attached or invalid", |
| port); |
| } |
| } |
| |
| test_mockable enum tcpc_cc_polarity pdc_power_mgmt_pd_get_polarity(int port) |
| { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| if (pdc_data[port]->port.connector_status.orientation) { |
| return POLARITY_CC2; |
| } |
| |
| return POLARITY_CC1; |
| } |
| |
| test_mockable enum pd_data_role pdc_power_mgmt_pd_get_data_role(int port) |
| { |
| /* Make sure port is connected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return PD_ROLE_DISCONNECTED; |
| } |
| |
| if (pdc_data[port]->port.connector_status.conn_partner_type == |
| DFP_ATTACHED) { |
| return PD_ROLE_UFP; |
| } |
| |
| return PD_ROLE_DFP; |
| } |
| |
| test_mockable enum pd_power_role pdc_power_mgmt_get_power_role(int port) |
| { |
| /* Make sure port is connected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return PD_ROLE_SINK; |
| } |
| |
| if (pdc_data[port]->port.connector_status.power_direction) { |
| return PD_ROLE_SOURCE; |
| } |
| |
| return PD_ROLE_SINK; |
| } |
| |
| enum pd_cc_states pdc_power_mgmt_get_task_cc_state(int port) |
| { |
| /* Make sure port is connected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return PD_CC_NONE; |
| } |
| |
| switch (pdc_data[port]->port.connector_status.conn_partner_type) { |
| case DFP_ATTACHED: |
| return PD_CC_DFP_ATTACHED; |
| case UFP_ATTACHED: |
| return PD_CC_UFP_ATTACHED; |
| case POWERED_CABLE_NO_UFP_ATTACHED: |
| return PD_CC_NONE; |
| case POWERED_CABLE_UFP_ATTACHED: |
| return PD_CC_UFP_ATTACHED; |
| case DEBUG_ACCESSORY_ATTACHED: |
| return PD_CC_UFP_DEBUG_ACC; |
| case AUDIO_ADAPTER_ACCESSORY_ATTACHED: |
| return PD_CC_UFP_AUDIO_ACC; |
| } |
| |
| return PD_CC_NONE; |
| } |
| |
| bool pdc_power_mgmt_pd_capable(int port) |
| { |
| /* Make sure port is connected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return false; |
| } |
| |
| return (pdc_data[port]->port.attached_state == SNK_ATTACHED_STATE) || |
| (pdc_data[port]->port.attached_state == SRC_ATTACHED_STATE); |
| } |
| |
| bool pdc_power_mgmt_get_partner_dual_role_power(int port) |
| { |
| /* Make sure port is connected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return false; |
| } |
| |
| return pdc_data[port]->port.ccaps.op_mode_drp; |
| } |
| |
| test_mockable bool pdc_power_mgmt_get_partner_data_swap_capable(int port) |
| { |
| struct pdc_port_t *pdc_port; |
| uint32_t fixed_vsafe5v_pdo; |
| |
| /* Make sure port is connected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return false; |
| } |
| |
| pdc_port = &pdc_data[port]->port; |
| |
| if (pdc_port->get_pdo.pdo_source != PARTNER_PDO) { |
| return false; |
| } |
| fixed_vsafe5v_pdo = |
| get_pdc_pdos_ptr(pdc_port, &pdc_port->get_pdo)->pdos[0]; |
| |
| /* |
| * Error check that first PDO is fixed, as 6.4.1 Capabilities requires |
| * in the Power Delivery Specification. |
| * "The vSafe5V Fixed Supply Object Shall always be the first object" |
| */ |
| if ((fixed_vsafe5v_pdo & PDO_TYPE_MASK) != PDO_TYPE_FIXED) |
| return false; |
| |
| return fixed_vsafe5v_pdo & PDO_FIXED_DATA_SWAP; |
| } |
| |
| int pdc_power_mgmt_get_vbus_voltage(int port) |
| { |
| /* Make sure port is connected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return 0; |
| } |
| |
| /* Return cached VBUS */ |
| return pdc_data[port]->port.vbus; |
| } |
| |
| test_mockable int pdc_power_mgmt_reset(int port) |
| { |
| int rv; |
| |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| /* Instruct the PDC driver to reset itself. This resets the driver to |
| * its initial state and re-runs the PDC setup routine commands. |
| */ |
| rv = public_api_block(port, CMD_PDC_RESET); |
| if (rv) { |
| return rv; |
| } |
| |
| /* Revert back to init state */ |
| set_pdc_state(&pdc_data[port]->port, PDC_INIT); |
| |
| return 0; |
| } |
| |
| test_mockable uint8_t pdc_power_mgmt_get_src_cap_cnt(int port) |
| { |
| /* Make sure port is Sink connected */ |
| if (!pdc_power_mgmt_is_sink_connected(port)) { |
| return 0; |
| } |
| |
| return pdc_data[port]->port.snk_policy.partner_src_pdos.pdo_count; |
| } |
| |
| test_mockable const uint32_t *const pdc_power_mgmt_get_src_caps(int port) |
| { |
| /* Make sure port is Sink connected */ |
| if (!pdc_power_mgmt_is_sink_connected(port)) { |
| return NULL; |
| } |
| |
| return (const uint32_t *const)pdc_data[port] |
| ->port.snk_policy.partner_src_pdos.pdos; |
| } |
| |
| test_mockable int pdc_power_mgmt_get_rdo(int port, uint32_t *rdo) |
| { |
| if (rdo == NULL) { |
| return -EINVAL; |
| } |
| |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| /* Make sure port is sink connected and in the run sub-state */ |
| if (!(pdc_data[port]->port.attached_state == SNK_ATTACHED_STATE && |
| pdc_data[port]->port.snk_attached_local_state == |
| SNK_ATTACHED_RUN)) { |
| return -ENODATA; |
| } |
| |
| *rdo = pdc_data[port]->port.connector_status.rdo; |
| return 0; |
| } |
| |
| static void pdc_power_mgmt_get_selected_pdo(int port, uint32_t *pdo, |
| uint32_t *max_ma, uint32_t *max_mv) |
| { |
| uint32_t pos = RDO_POS(pdc_data[port]->port.connector_status.rdo) - 1; |
| uint32_t *pdos = pdc_data[port]->port.snk_policy.partner_src_pdos.pdos; |
| uint32_t tmp; |
| |
| *pdo = pdos[pos]; |
| pd_extract_pdo_power(*pdo, max_ma, max_mv, &tmp); |
| } |
| |
| uint32_t pdc_power_mgmt_get_requested_voltage(int port) |
| { |
| uint32_t pdo, max_ma, max_mv; |
| |
| /* Make sure port is Sink connected and RDO is valid */ |
| if (!pdc_power_mgmt_is_sink_connected(port) || |
| !pdc_is_rdo_valid(&pdc_data[port]->port.connector_status)) { |
| return 0; |
| } |
| |
| pdc_power_mgmt_get_selected_pdo(port, &pdo, &max_ma, &max_mv); |
| |
| return max_mv; |
| } |
| |
| uint32_t pdc_power_mgmt_get_requested_current(int port) |
| { |
| uint32_t pdo, max_ma, max_mv; |
| |
| /* Make sure port is Sink connected and RDO is valid */ |
| if (!pdc_power_mgmt_is_sink_connected(port) || |
| !pdc_is_rdo_valid(&pdc_data[port]->port.connector_status)) { |
| return 0; |
| } |
| |
| pdc_power_mgmt_get_selected_pdo(port, &pdo, &max_ma, &max_mv); |
| |
| return max_ma; |
| } |
| |
| test_mockable const char *pdc_power_mgmt_get_task_state_name(int port) |
| { |
| enum pdc_state_t state = pdc_power_mgmt_get_task_state(port); |
| |
| return pdc_state_names[state]; |
| } |
| |
| test_mockable void pdc_power_mgmt_set_dual_role(int port, |
| enum pd_dual_role_states state) |
| { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| LOG_ERR("%s called with bad port C%d.", __func__, port); |
| return; |
| } |
| |
| struct pdc_port_t *port_data = &pdc_data[port]->port; |
| |
| LOG_INF("C%d: pdc_power_mgmt_set_dual_role: set role to %d", port, |
| state); |
| |
| /* |
| * clears the flags set in this function in case multiple consecutive |
| * calls to set_dual_role is made to prevent multiple roles being |
| * active at the same time. |
| */ |
| atomic_clear_bit(port_data->src_policy.flags, SRC_POLICY_FORCE_SNK); |
| atomic_clear_bit(port_data->src_policy.flags, SRC_POLICY_SWAP_TO_SNK); |
| atomic_clear_bit(port_data->snk_policy.flags, SNK_POLICY_SWAP_TO_SRC); |
| |
| switch (state) { |
| /* While disconnected, toggle between src and sink */ |
| case PD_DRP_TOGGLE_ON: |
| /* Allow external power role swaps */ |
| port_data->src_policy.accept_power_role_swap = true; |
| port_data->snk_policy.accept_power_role_swap = true; |
| |
| port_data->una_policy.cc_mode = CCOM_DRP; |
| port_data->drp = port_data->una_policy.drp_mode; |
| atomic_set_bit(port_data->una_policy.flags, UNA_POLICY_CC_MODE); |
| break; |
| /* Stay in src until disconnect, then stay in sink forever */ |
| case PD_DRP_TOGGLE_OFF: |
| /* Allow external power role swap from source to sink, but not |
| * the reverse */ |
| port_data->src_policy.accept_power_role_swap = true; |
| port_data->snk_policy.accept_power_role_swap = false; |
| |
| port_data->una_policy.cc_mode = CCOM_RD; |
| atomic_set_bit(port_data->una_policy.flags, UNA_POLICY_CC_MODE); |
| break; |
| /* Stay in current power role, don't switch. No auto-toggle support */ |
| case PD_DRP_FREEZE: |
| /* No external power role swaps accepted */ |
| port_data->src_policy.accept_power_role_swap = false; |
| port_data->snk_policy.accept_power_role_swap = false; |
| |
| if (pdc_power_mgmt_is_source_connected(port)) { |
| port_data->una_policy.cc_mode = CCOM_RP; |
| } else { |
| port_data->una_policy.cc_mode = CCOM_RD; |
| } |
| atomic_set_bit(port_data->una_policy.flags, UNA_POLICY_CC_MODE); |
| break; |
| /* Switch to sink */ |
| case PD_DRP_FORCE_SINK: |
| /* Allow external power role swap from src to sink */ |
| port_data->src_policy.accept_power_role_swap = true; |
| port_data->snk_policy.accept_power_role_swap = false; |
| |
| if (pdc_power_mgmt_is_source_connected(port)) { |
| atomic_set_bit(port_data->src_policy.flags, |
| SRC_POLICY_SWAP_TO_SNK); |
| } |
| |
| /* |
| * If PRS to Sink fails, or if not connected via PD, disconnect |
| * and reconnect as Sink. |
| */ |
| port_data->una_policy.cc_mode = CCOM_RD; |
| port_data->drp = DRP_NORMAL; |
| atomic_set_bit(port_data->una_policy.flags, |
| UNA_POLICY_DRP_MODE); |
| atomic_set_bit(port_data->una_policy.flags, UNA_POLICY_CC_MODE); |
| atomic_set_bit(port_data->src_policy.flags, |
| SRC_POLICY_FORCE_SNK); |
| break; |
| /* Switch to source */ |
| case PD_DRP_FORCE_SOURCE: |
| /* Allow external power role swap from sink to src */ |
| port_data->src_policy.accept_power_role_swap = false; |
| port_data->snk_policy.accept_power_role_swap = true; |
| |
| if (pdc_power_mgmt_is_sink_connected(port)) { |
| atomic_set_bit(port_data->snk_policy.flags, |
| SNK_POLICY_SWAP_TO_SRC); |
| } |
| break; |
| default: |
| LOG_INF("C%d: Invalid dual-role state %d. Ignoring.", port, |
| state); |
| return; |
| } |
| |
| /* Trigger updates to the power role swap allow bit */ |
| atomic_set_bit(port_data->src_policy.flags, |
| SRC_POLICY_UPDATE_ALLOW_PR_SWAP); |
| atomic_set_bit(port_data->snk_policy.flags, |
| SNK_POLICY_UPDATE_ALLOW_PR_SWAP); |
| |
| port_data->dual_role_state = state; |
| } |
| |
| test_mockable enum pd_dual_role_states pdc_power_mgmt_get_dual_role(int port) |
| { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| struct pdc_port_t *port_data = &pdc_data[port]->port; |
| |
| return port_data->dual_role_state; |
| } |
| |
| test_mockable int pdc_power_mgmt_set_trysrc(int port, bool enable) |
| { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| LOG_INF("C%d: PD setting TrySrc=%d", port, enable); |
| |
| pdc_data[port]->port.drp = (enable ? DRP_TRY_SRC : DRP_NORMAL); |
| |
| return public_api_block(port, CMD_PDC_SET_DRP); |
| } |
| |
| static void set_hpd_wake_watch(int port) |
| { |
| struct pdc_port_t *port_data = &pdc_data[port]->port; |
| |
| /* Only watch for HPD wake when connected to a DP Alt Mode partner with |
| * HPD_LVL low. |
| */ |
| port_data->hpd_wake_watch = false; |
| if (!pdc_power_mgmt_pd_capable(port) || |
| !(port_data->connector_status.conn_partner_flags & |
| CONNECTOR_PARTNER_FLAG_ALTERNATE_MODE) || |
| PD_VDO_DPSTS_HPD_LVL(port_data->attention_vdo.vdo)) { |
| return; |
| } |
| |
| port_data->hpd_wake_watch = true; |
| } |
| |
| static void clear_hpd_wake_watch(int port) |
| { |
| struct pdc_port_t *port_data = &pdc_data[port]->port; |
| port_data->hpd_wake_watch = false; |
| } |
| |
| /** |
| * PDC Chipset state Policies |
| */ |
| |
| static void pd_chipset_resume(void) |
| { |
| k_work_reschedule(&pdc_apply_power_state_policy_work, |
| PDC_POWER_STATE_DEBOUNCE_MS); |
| |
| LOG_INF("PD: S3->S0"); |
| } |
| DECLARE_HOOK(HOOK_CHIPSET_RESUME, pd_chipset_resume, HOOK_PRIO_DEFAULT); |
| |
| static void pd_chipset_suspend(void) |
| { |
| k_work_reschedule(&pdc_apply_power_state_policy_work, |
| PDC_POWER_STATE_DEBOUNCE_MS); |
| |
| LOG_INF("PD: S0->S3"); |
| } |
| DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, pd_chipset_suspend, HOOK_PRIO_DEFAULT); |
| |
| static void pd_chipset_startup(void) |
| { |
| k_work_reschedule(&pdc_apply_power_state_policy_work, |
| PDC_POWER_STATE_DEBOUNCE_MS); |
| |
| LOG_INF("PD: S5->S3"); |
| } |
| DECLARE_HOOK(HOOK_CHIPSET_STARTUP, pd_chipset_startup, HOOK_PRIO_DEFAULT); |
| |
| static void pd_chipset_shutdown(void) |
| { |
| k_work_reschedule(&pdc_apply_power_state_policy_work, |
| PDC_POWER_STATE_DEBOUNCE_MS); |
| |
| LOG_INF("PD: S3->S5"); |
| } |
| #ifdef CONFIG_PLATFORM_EC_CHIPSET_RESUME_INIT_HOOK |
| DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN_COMPLETE, pd_chipset_shutdown, |
| HOOK_PRIO_DEFAULT); |
| #else |
| DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, pd_chipset_shutdown, HOOK_PRIO_DEFAULT); |
| #endif |
| |
| static void pdc_battery_status_changed(void) |
| { |
| for (int i = 0; i < pdc_power_mgmt_get_usb_pd_port_count(); i++) { |
| if (pdc_power_mgmt_is_pdc_port_valid(i)) { |
| pdc_update_battery_status(&pdc_data[i]->port, false); |
| } |
| } |
| } |
| DECLARE_HOOK(HOOK_BATTERY_SOC_CHANGE, pdc_battery_status_changed, |
| HOOK_PRIO_DEFAULT); |
| |
| /** |
| * @brief Send updated battery status to the PDC |
| * |
| * @param port PDC port to update |
| * @param force If true, unconditionally send the battery status. Otherwise |
| * only send battery status if the battery information changed. |
| */ |
| static void pdc_update_battery_status(struct pdc_port_t *port, bool force) |
| { |
| const struct pdc_config_t *config = port->dev->config; |
| int port_number = config->connector_num; |
| union battery_status_t bsdo = { 0 }; |
| |
| if (battery_is_present() == BP_YES) { |
| uint32_t v; |
| uint32_t c; |
| |
| if (battery_design_voltage(&v) != 0 || |
| battery_remaining_capacity(&c) != 0) { |
| bsdo.present_capacity = BSDO_CAP_UNKNOWN; |
| } else { |
| /* |
| * Wh = (c * v) / 1000000 |
| * 10th of a Wh = Wh * 10 |
| */ |
| bsdo.present_capacity = |
| DIV_ROUND_NEAREST((c * v), 100000); |
| } |
| |
| /* Battery is present */ |
| bsdo.battery_present = 1; |
| |
| /* |
| * For drivers that are not smart battery compliant, |
| * battery_status() returns EC_ERROR_UNIMPLEMENTED and |
| * the battery is assumed to be idle. |
| */ |
| if (battery_status(&c) != 0) { |
| /* Assume idle if battery status is not available. */ |
| bsdo.battery_state = BSDO_BATTERY_STATE_IDLE; |
| } else { |
| if (c & STATUS_FULLY_CHARGED) { |
| /* Fully charged */ |
| bsdo.battery_state = BSDO_BATTERY_STATE_IDLE; |
| } else if (c & STATUS_DISCHARGING) { |
| /* Discharging */ |
| bsdo.battery_state = |
| BSDO_BATTERY_STATE_DISCHARGING; |
| } else { |
| /* Else battery is charging.*/ |
| bsdo.battery_state = |
| BSDO_BATTERY_STATE_CHARGING; |
| } |
| } |
| } else { |
| bsdo.battery_present = 0; |
| bsdo.present_capacity = BSDO_CAP_UNKNOWN; |
| } |
| |
| if (!force && memcmp(&port->bstat, &bsdo, sizeof(bsdo)) == 0) { |
| return; |
| } |
| |
| port->bstat = bsdo; |
| if (pdc_power_mgmt_is_sink_connected(port_number)) { |
| atomic_set_bit(port->snk_policy.flags, |
| SNK_POLICY_UPDATE_BATTERY_STATUS); |
| } else if (pdc_power_mgmt_is_source_connected(port_number)) { |
| atomic_set_bit(port->src_policy.flags, |
| SRC_POLICY_UPDATE_BATTERY_STATUS); |
| } |
| } |
| |
| static void pdc_update_battery_capability(struct pdc_port_t *port) |
| { |
| const struct pdc_config_t *config = port->dev->config; |
| int port_number = config->connector_num; |
| union battery_capability_t bcdb = { 0 }; |
| |
| /* TODO: Replace with proper VID:PID API */ |
| /* Set VID */ |
| bcdb.vid = CONFIG_PLATFORM_EC_USB_VID; |
| |
| /* Set PID */ |
| bcdb.pid = CONFIG_PLATFORM_EC_USB_PID; |
| |
| if (battery_is_present() == BP_YES) { |
| uint32_t v; |
| uint32_t c; |
| |
| /* |
| * The Battery Design Capacity field shall return the |
| * Battery’s design capacity in tenths of Wh. If the |
| * Battery is Hot Swappable and is not present, the |
| * Battery Design Capacity field shall be set to 0. If |
| * the Battery is unable to report its Design Capacity, |
| * it shall return 0xFFFF |
| */ |
| bcdb.design_capacity = 0xffff; |
| |
| /* |
| * The Battery Last Full Charge Capacity field shall |
| * return the Battery’s last full charge capacity in |
| * tenths of Wh. If the Battery is Hot Swappable and |
| * is not present, the Battery Last Full Charge Capacity |
| * field shall be set to 0. If the Battery is unable to |
| * report its Design Capacity, the Battery Last Full |
| * Charge Capacity field shall be set to 0xFFFF. |
| */ |
| bcdb.last_full_charge_capacity = 0xffff; |
| |
| if (battery_design_voltage(&v) == 0) { |
| if (battery_design_capacity(&c) == 0) { |
| /* |
| * Wh = (c * v) / 1000000 |
| * 10th of a Wh = Wh * 10 |
| */ |
| bcdb.design_capacity = |
| DIV_ROUND_NEAREST((c * v), 100000); |
| } |
| |
| if (battery_full_charge_capacity(&c) == 0) { |
| /* |
| * Wh = (c * v) / 1000000 |
| * 10th of a Wh = Wh * 10 |
| */ |
| bcdb.last_full_charge_capacity = |
| DIV_ROUND_NEAREST((c * v), 100000); |
| } |
| } |
| } |
| |
| port->bcap = bcdb; |
| if (pdc_power_mgmt_is_sink_connected(port_number)) { |
| atomic_set_bit(port->snk_policy.flags, |
| SNK_POLICY_UPDATE_BATTERY_CAPABILITY); |
| } else if (pdc_power_mgmt_is_source_connected(port_number)) { |
| atomic_set_bit(port->src_policy.flags, |
| SRC_POLICY_UPDATE_BATTERY_CAPABILITY); |
| } |
| } |
| |
| test_mockable int pdc_power_mgmt_get_drp_mode(int port, |
| enum drp_mode_t *drp_mode) |
| { |
| int ret; |
| |
| /* Make sure port is in range and that an output buffer is provided */ |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| if (drp_mode == NULL) { |
| return -EINVAL; |
| } |
| |
| ret = public_api_block(port, CMD_PDC_GET_DRP); |
| if (ret) { |
| return ret; |
| } |
| |
| *drp_mode = pdc_data[port]->port.drp_read; |
| return 0; |
| } |
| |
| test_mockable int pdc_power_mgmt_get_info(int port, struct pdc_info_t *pdc_info, |
| bool live) |
| { |
| int ret; |
| |
| /* Make sure port is in range and that an output buffer is provided */ |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| if (pdc_info == NULL) { |
| return -EINVAL; |
| } |
| |
| if (live) { |
| /* Caller wants live chip info. Set up a public API call to |
| * retrieve it from the PDC. |
| */ |
| ret = public_api_block(port, CMD_PDC_GET_INFO); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Provide a copy of the current info struct to avoid exposing |
| * internal data structs. |
| */ |
| memcpy(pdc_info, &pdc_data[port]->port.info, |
| sizeof(struct pdc_info_t)); |
| return 0; |
| } |
| |
| /* Non-live requests can be handled synchronously by calling directly |
| * into the PDC driver. |
| */ |
| return pdc_get_info(pdc_data[port]->port.pdc, pdc_info, false); |
| } |
| |
| test_mockable int pdc_power_mgmt_get_lpm_ppm_info(int port, |
| struct lpm_ppm_info_t *info) |
| { |
| int ret; |
| |
| /* Make sure port is in range and that an output buffer is provided */ |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| if (info == NULL) { |
| return -EINVAL; |
| } |
| |
| pdc_data[port]->port.lpm_ppm_info = info; |
| |
| ret = public_api_block(port, CMD_PDC_GET_LPM_PPM_INFO); |
| if (ret) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| int pdc_power_mgmt_get_hw_config(int port, |
| struct pdc_hw_config_t *pdc_hw_config) |
| { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| /* This operation is handled synchronously within the driver based on |
| * compile-time data. No need to block or go through the state machine. |
| */ |
| |
| return pdc_get_hw_config(pdc_data[port]->port.pdc, pdc_hw_config); |
| } |
| |
| int pdc_power_mgmt_get_rev(int port, enum tcpci_msg_type type) |
| { |
| uint32_t rev; |
| |
| /* Make sure port is connected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return 0; |
| } |
| |
| switch (type) { |
| case TCPCI_MSG_SOP: |
| rev = pdc_data[port]->port.ccaps.partner_pd_revision; |
| break; |
| case TCPCI_MSG_SOP_PRIME: |
| rev = pdc_data[port]->port.cable_prop.cable_pd_revision; |
| break; |
| default: |
| rev = 0; |
| } |
| |
| return rev; |
| } |
| |
| const uint32_t *const pdc_power_mgmt_get_snk_caps(int port) |
| { |
| /* Make sure port is Sink connected */ |
| if (!pdc_power_mgmt_is_source_connected(port)) { |
| return NULL; |
| } |
| |
| return (const uint32_t *const)pdc_data[port] |
| ->port.src_policy.partner_snk_pdos.pdos; |
| } |
| |
| uint8_t pdc_power_mgmt_get_snk_cap_cnt(int port) |
| { |
| /* Make sure port is Sink connected */ |
| if (!pdc_power_mgmt_is_source_connected(port)) { |
| return 0; |
| } |
| |
| return pdc_data[port]->port.src_policy.partner_snk_pdos.pdo_count; |
| } |
| |
| struct rmdo pdc_power_mgmt_get_partner_rmdo(int port) |
| { |
| struct rmdo value = { 0 }; |
| |
| /* The PD 3.1 Get_Revision Message is optional and currently not |
| * supported in the PDC, although this may change in future updates. */ |
| |
| return value; |
| } |
| |
| enum pd_discovery_state |
| pdc_power_mgmt_get_identity_discovery(int port, enum tcpci_msg_type type) |
| { |
| enum pdc_cmd_t cmd; |
| int ret; |
| |
| /* Make sure port is Sink connected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return PD_DISC_NEEDED; |
| } |
| |
| switch (type) { |
| case TCPCI_MSG_SOP: |
| cmd = CMD_PDC_GET_IDENTITY_DISCOVERY; |
| break; |
| case TCPCI_MSG_SOP_PRIME: |
| return (pdc_data[port]->port.cable_prop.cable_type && |
| pdc_data[port]->port.cable_prop.mode_support) ? |
| PD_DISC_COMPLETE : |
| PD_DISC_FAIL; |
| default: |
| return PD_DISC_FAIL; |
| } |
| |
| /* Block until command completes */ |
| ret = public_api_block(port, cmd); |
| if (ret) { |
| return PD_DISC_NEEDED; |
| } |
| |
| return pdc_data[port]->port.discovery_state ? PD_DISC_COMPLETE : |
| PD_DISC_FAIL; |
| } |
| |
| test_mockable int |
| pdc_power_mgmt_connector_reset(int port, enum connector_reset reset_type) |
| { |
| /* Make sure port is in range and that an output buffer is provided */ |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| /* Make sure port is connected */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return EC_SUCCESS; |
| } |
| |
| pdc_data[port]->port.connector_reset.raw_value = 0; |
| pdc_data[port]->port.connector_reset.reset_type = reset_type; |
| |
| /* Block until command completes */ |
| return public_api_block(port, CMD_PDC_CONNECTOR_RESET); |
| } |
| |
| static int pdc_run_get_discovery(int port) |
| { |
| int ret; |
| |
| /* Make sure port is in range and that an output buffer is provided */ |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| /* Make sure port is connected and PD capable */ |
| if (!pdc_power_mgmt_is_connected(port) || |
| !pdc_power_mgmt_pd_capable(port)) { |
| return 0; |
| } |
| |
| /* Format the GET_VDO command */ |
| discovery_info_init(&pdc_data[port]->port); |
| |
| /* Block until command completes */ |
| ret = public_api_block(port, CMD_PDC_GET_VDO); |
| if (ret) { |
| return ret; |
| } |
| |
| LOG_INF("GET_VDO[%d]: vid = %04x, pid = %04x, prod_type = %d", port, |
| PD_IDH_VID(pdc_data[port]->port.vdo[0]), |
| PD_PRODUCT_PID(pdc_data[port]->port.vdo[1]), |
| PD_IDH_PTYPE(pdc_data[port]->port.vdo[0])); |
| |
| return 0; |
| } |
| |
| uint16_t pdc_power_mgmt_get_identity_vid(int port) |
| { |
| uint16_t vid = 0; |
| struct pdc_port_t *pdc; |
| |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return vid; |
| } |
| |
| pdc = &pdc_data[port]->port; |
| /* |
| * TODO(b/327283662); GET_VDO completes with 0 length bytes to read |
| * |
| * The VDOs should be retrieved as part of either the src_attached or |
| * snk_attached state flows. However, if the port is connected during an |
| * EC reboot, then the GET_VDO command will complete successfully, but |
| * indicates a 0 VDO length and so the ST_READ state is skipped in the |
| * driver. Adding a work-around here such that if the first VDO is all |
| * 0s, then trigger another GET_VDO command in order to get the values |
| * required. GET_VDO is only sent, if the port is connected and pd |
| * capable. |
| * |
| */ |
| if (pdc->vdo[IDENTITY_VID_VDO_IDX] == INVALID_VDO_VALUE) { |
| pdc_run_get_discovery(port); |
| } |
| |
| if (pdc->vdo[IDENTITY_VID_VDO_IDX]) { |
| vid = PD_IDH_VID(pdc->vdo[IDENTITY_VID_VDO_IDX]); |
| } |
| |
| return vid; |
| } |
| |
| uint16_t pdc_power_mgmt_get_identity_pid(int port) |
| { |
| uint16_t pid = 0; |
| struct pdc_port_t *pdc; |
| |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return pid; |
| } |
| |
| pdc = &pdc_data[port]->port; |
| |
| if (pdc->vdo[IDENTITY_VID_VDO_IDX] == INVALID_VDO_VALUE) { |
| pdc_run_get_discovery(port); |
| } |
| |
| if (pdc->vdo[IDENTITY_PID_VDO_IDX]) { |
| pid = PD_PRODUCT_PID(pdc->vdo[IDENTITY_PID_VDO_IDX]); |
| } |
| |
| return pid; |
| } |
| |
| uint8_t pdc_power_mgmt_get_product_type(int port) |
| { |
| uint8_t ptype = 0; |
| struct pdc_port_t *pdc; |
| |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return ptype; |
| } |
| |
| pdc = &pdc_data[port]->port; |
| |
| if (pdc->vdo[IDENTITY_PTYPE_VDO_IDX] == INVALID_VDO_VALUE) { |
| pdc_run_get_discovery(port); |
| } |
| |
| if (pdc->vdo[IDENTITY_PTYPE_VDO_IDX]) { |
| ptype = PD_IDH_PTYPE(pdc->vdo[IDENTITY_PTYPE_VDO_IDX]); |
| } |
| |
| return ptype; |
| } |
| |
| /** Allow 3s for the PDC SM to suspend itself. */ |
| #define SUSPEND_TIMEOUT_USEC (3 * USEC_PER_SEC) |
| |
| #ifdef CONFIG_USBC_PDC_DRIVEN_CCD |
| /** Store the CCD port's SBU mux operating mode when suspending */ |
| static enum pdc_sbu_mux_mode sbu_mux_mode_at_suspend = PDC_SBU_MUX_MODE_NORMAL; |
| #endif /* CONFIG_USBC_PDC_DRIVEN_CCD */ |
| |
| /* TODO(b/323371550): These functions should be adjusted to target individual PD |
| * chips rather than all ports at once. It should take a chip ID as a param and |
| * track current comms status by chip. |
| */ |
| |
| /** |
| * @brief Suspend/disable communication to the PDC |
| */ |
| static int suspend_pdc_comms(void) |
| { |
| int ret; |
| int status = 0; |
| |
| uint8_t port_count = pdc_power_mgmt_get_usb_pd_port_count(); |
| |
| #ifdef CONFIG_USBC_PDC_DRIVEN_CCD |
| /* Save current SBU mux override state so we can restore upon |
| * PDC subsystem resume. */ |
| ret = pdc_power_mgmt_get_sbu_mux_mode(&sbu_mux_mode_at_suspend, NULL); |
| if (ret) { |
| LOG_ERR("PD: Cannot read current SBU mux mode: %d", ret); |
| } else { |
| LOG_INF("PD: Save SBU mux mode of %d", sbu_mux_mode_at_suspend); |
| } |
| #endif /* CONFIG_USBC_PDC_DRIVEN_CCD */ |
| |
| /* Request each port's PDC state machine to enter the suspend |
| * state. |
| */ |
| for (int p = 0; p < port_count; p++) { |
| atomic_set(&pdc_data[p]->port.suspend, 1); |
| } |
| |
| /* Wait for each PDC state machine to enter suspended state */ |
| for (int p = 0; p < port_count; p++) { |
| if (get_pdc_state(&pdc_data[p]->port) == PDC_DISABLED) { |
| /* Ignore disabled ports */ |
| continue; |
| } |
| |
| ret = WAIT_FOR( |
| get_pdc_state(&pdc_data[p]->port) == PDC_SUSPENDED, |
| SUSPEND_TIMEOUT_USEC, k_sleep(K_MSEC(LOOP_DELAY_MS))); |
| if (!ret) { |
| LOG_ERR("Timed out suspending PDC SM for port " |
| "C%d: %d", |
| p, ret); |
| status = -ETIMEDOUT; |
| } |
| } |
| |
| /* Suspend the driver layer */ |
| for (int p = 0; p < port_count; p++) { |
| if (get_pdc_state(&pdc_data[p]->port) == PDC_DISABLED) { |
| /* Ignore disabled ports */ |
| continue; |
| } |
| |
| ret = pdc_set_comms_state(pdc_data[p]->port.pdc, false); |
| |
| if (ret) { |
| LOG_ERR("Cannot suspend port C%d driver: %d", p, ret); |
| status = ret; |
| } |
| } |
| |
| return status; |
| } |
| |
| /** |
| * @brief Resume/enable communication to the PDC |
| */ |
| static int resume_pdc_comms(void) |
| { |
| int ret; |
| int status = 0; |
| |
| uint8_t port_count = pdc_power_mgmt_get_usb_pd_port_count(); |
| |
| #ifdef CONFIG_USBC_PDC_DRIVEN_CCD |
| if (sbu_mux_mode_at_suspend == PDC_SBU_MUX_MODE_FORCE_DBG) { |
| /* Set a flag on the CCD port to go back into force-debug mode. |
| * This cannot be done immediately because the drivers need time |
| * to re-initialize. If the previous SBU mux mode was normal |
| * operation, that is the default and no action is necessary. |
| */ |
| int ccd_port = pdc_power_mgmt_get_ccd_port(); |
| |
| LOG_INF("PD: Restore C%d (CCD) SBU mux to forced-debug mode", |
| ccd_port); |
| atomic_set_bit(pdc_data[ccd_port]->port.common_policy.flags, |
| COMMON_POLICY_SET_SBU_MUX_TO_FORCED_DEBUG); |
| } |
| #endif /* CONFIG_USBC_PDC_DRIVEN_CCD */ |
| |
| /* Resume and reset the driver layer */ |
| for (int p = 0; p < port_count; p++) { |
| if (get_pdc_state(&pdc_data[p]->port) == PDC_DISABLED) { |
| /* Ignore disabled ports */ |
| continue; |
| } |
| |
| ret = pdc_set_comms_state(pdc_data[p]->port.pdc, true); |
| if (ret) { |
| LOG_ERR("Cannot resume port C%d driver: %d", p, ret); |
| status = ret; |
| } |
| } |
| |
| /* Release each PDC state machine. A reset is performed when |
| * exiting the suspended state. |
| */ |
| for (int p = 0; p < port_count; p++) { |
| atomic_set(&pdc_data[p]->port.suspend, 0); |
| } |
| |
| return status; |
| } |
| |
| test_mockable int pdc_power_mgmt_set_comms_state(bool enable_comms) |
| { |
| int ret; |
| |
| static bool current_comms_status = true; |
| |
| if (enable_comms == current_comms_status) { |
| LOG_ERR("PD: Unnecessary suspend or resume. " |
| "Current state already %d", |
| current_comms_status); |
| return -EALREADY; |
| } |
| |
| if (enable_comms) { |
| LOG_INF("PD: Resume PDC communication"); |
| ret = resume_pdc_comms(); |
| } else { |
| LOG_INF("PD: Suspend PDC communication"); |
| ret = suspend_pdc_comms(); |
| } |
| |
| if (ret == 0) { |
| /* Successfully changed comms state */ |
| current_comms_status = enable_comms; |
| } |
| |
| return ret; |
| } |
| |
| test_mockable int |
| pdc_power_mgmt_get_connector_status(int port, |
| union connector_status_t *connector_status) |
| { |
| struct pdc_port_t *pdc; |
| |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| if (connector_status == NULL) { |
| return -EINVAL; |
| } |
| |
| pdc = &pdc_data[port]->port; |
| |
| *connector_status = pdc->connector_status; |
| |
| return 0; |
| } |
| |
| test_mockable int pdc_power_mgmt_get_last_status_change( |
| int port, union conn_status_change_bits_t *status_change) |
| { |
| struct pdc_port_t *pdc; |
| |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| if (status_change == NULL) { |
| return -EINVAL; |
| } |
| |
| pdc = &pdc_data[port]->port; |
| |
| status_change->raw_value = pdc->ci.raw_value; |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_PLATFORM_EC_USB_PD_DP_MODE |
| uint8_t pdc_power_mgmt_get_dp_pin_mode(int port) |
| { |
| uint8_t pin_mode; |
| |
| /* Make sure port is in range and that an output buffer is provided */ |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| LOG_ERR("get_dp_pin_mode: invalid port %d", port); |
| return 0; |
| } |
| |
| /* Make sure port is connected and PD capable */ |
| if (!pdc_power_mgmt_is_connected(port)) { |
| return 0; |
| } |
| |
| /* Check if Alternate Mode is actually active */ |
| if (!(pdc_data[port]->port.connector_status.conn_partner_flags & |
| CONNECTOR_PARTNER_FLAG_ALTERNATE_MODE)) { |
| return 0; |
| } |
| |
| /* |
| * Byte 1 (bits 15:8) contains the DP Source Device Pin assignment. |
| * The VDO pin assignments match our MODE_DP_PIN_x definitions. |
| */ |
| pin_mode = (pdc_data[port]->port.vdo_dp_cfg >> 8) & 0xFF; |
| |
| LOG_INF("C%d: DP pin mode 0x%02x", port, pin_mode); |
| |
| return pin_mode; |
| } |
| |
| mux_state_t pdc_power_mgmt_get_dp_mux_mode(int port) |
| { |
| int pin_mode = get_dp_pin_mode(port); |
| /* Default dp_port_mf_allow is true */ |
| int mf_pref = PD_VDO_DPSTS_MF_PREF(pdc_power_mgmt_get_dp_status(port)); |
| |
| /* |
| * Multi-function operation is only allowed if that pin config is |
| * supported. |
| */ |
| if ((pin_mode & MODE_DP_PIN_MF_MASK) && mf_pref) |
| return USB_PD_MUX_DOCK; |
| else |
| return USB_PD_MUX_DP_ENABLED; |
| } |
| #endif |
| |
| void pdc_power_mgmt_set_max_voltage(unsigned int mv) |
| { |
| unsigned int max_mw; |
| if (mv < PD_MIN_MV || mv > CONFIG_PLATFORM_EC_USB_PD_MAX_VOLTAGE_MV) { |
| LOG_ERR("PD: Ignore invalid voltage request of %umV " |
| "(allowed range %u-%umV)", |
| mv, PD_MIN_MV, |
| CONFIG_PLATFORM_EC_USB_PD_MAX_VOLTAGE_MV); |
| return; |
| } |
| |
| LOG_INF("PD: New maximum voltage: %dmV", mv); |
| |
| pdc_max_request_mv = mv; |
| |
| /* Adjust our sink PDOs to the new maximum voltage and adjust the max |
| * power so we don't exceed the board current limit. |
| */ |
| max_mw = pdc_max_request_mv * CONFIG_PLATFORM_EC_USB_PD_MAX_CURRENT_MA / |
| 1000; |
| |
| pdc_snk_pdos.pdos[SNK_PDO_BATT_POS] = |
| PDO_BATT(4750, pdc_max_request_mv, max_mw); |
| pdc_snk_pdos.pdos[SNK_PDO_VAR_POS] = |
| PDO_VAR(4750, pdc_max_request_mv, |
| CONFIG_PLATFORM_EC_USB_PD_MAX_CURRENT_MA); |
| |
| /* All ports need to set new SINK PDOs */ |
| for (int i = 0; i < pdc_power_mgmt_get_usb_pd_port_count(); i++) { |
| atomic_set_bit(pdc_data[i]->port.cci_flags, CCI_SET_SINK_PDOS); |
| } |
| } |
| |
| test_mockable unsigned int pdc_power_mgmt_get_max_voltage(void) |
| { |
| return pdc_max_request_mv; |
| } |
| |
| test_mockable void pdc_power_mgmt_request_source_voltage(int port, int mv) |
| { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| LOG_ERR("%s: Invalid port parameter %d", __func__, port); |
| return; |
| } |
| |
| pdc_power_mgmt_set_max_voltage(mv); |
| |
| /* pdc_power_mgmt_set_max_voltage() triggers a SET_PDOS command |
| * to set new SINK PDOs for the LPM. |
| * |
| * If we are SNK_ATTACHED, trigger a re-evaluation of the SRC CAPS |
| * based on the new voltage limit. This generates a SET_RDO message |
| * to initiate a new Request message with the partner and set a new |
| * contract. |
| * |
| * TODO: b/494687197 - remove the call to |
| * pdc_power_mgmt_set_new_power_request() once all PDCs are verified |
| * to automatically negotiate a new contract when the EC sets new |
| * SINK PDOs in the LPM. |
| */ |
| if (pdc_power_mgmt_is_sink_connected(port)) { |
| pdc_power_mgmt_set_new_power_request(port); |
| } else if (pdc_power_mgmt_is_source_connected(port)) { |
| /* We are a source, swap to sink. The CCI_SET_SINK_PDOS |
| * will be handled prior to the check of the source policy |
| * flags, which ensures the new sink PDOs are in place prior |
| * the the power swap to sink. |
| */ |
| LOG_INF("C%d: Swapping to sink role to request new " |
| "source voltage", |
| port); |
| pdc_power_mgmt_request_power_swap(port); |
| } |
| |
| /* For all other states (non-PD and unattached), the next time |
| * we connect as SNK_ATTACHED, the PDC will use most recent SINK PDOS. |
| */ |
| } |
| |
| test_mockable int |
| pdc_power_mgmt_get_cable_prop(int port, union cable_property_t *cable_prop) |
| { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| if (cable_prop == NULL) { |
| return -EINVAL; |
| } |
| |
| *cable_prop = pdc_data[port]->port.cable_prop; |
| |
| return 0; |
| } |
| |
| void pdc_power_mgmt_request_tbt_reset(int port_num) |
| { |
| struct pdc_port_t *port; |
| |
| if (!pdc_power_mgmt_is_pdc_port_valid(port_num)) { |
| return; |
| } |
| |
| port = &pdc_data[port_num]->port; |
| |
| if (!atomic_test_bit(port->src_policy.flags, SRC_POLICY_TBT_RESET)) { |
| atomic_set_bit(port->src_policy.flags, SRC_POLICY_TBT_RESET); |
| k_event_post(&port->sm_event, PDC_SM_EVENT); |
| } |
| } |
| |
| __overridable enum usb_typec_current_t |
| pdc_power_mgmt_get_default_current_limit(int port) |
| { |
| return TC_CURRENT_1_5A; |
| } |
| |
| /** |
| * @brief Adjust typec and USB-PD current limits |
| */ |
| int pdc_power_mgmt_set_current_limit(int port_num, |
| enum usb_typec_current_t current) |
| { |
| struct pdc_port_t *pdc; |
| |
| if (!pdc_power_mgmt_is_pdc_port_valid(port_num)) { |
| return -ERANGE; |
| } |
| |
| pdc = &pdc_data[port_num]->port; |
| |
| /* Always set the new Rp value */ |
| pdc->una_policy.tcc = current; |
| |
| /* Always set the new SRC PDO. */ |
| pdc->src_policy.lpm_src_pdo = current == TC_CURRENT_3_0A ? |
| pdc_src_pdo_max : |
| pdc_src_pdo_nominal; |
| |
| LOG_INF("C%d: set current limit %s", port_num, |
| (current == TC_CURRENT_3_0A) ? "3.0A" : "1.5A"); |
| |
| /* Further actions depend on the port attached state and power role */ |
| switch (pdc->attached_state) { |
| case INIT_STATE: |
| break; |
| case SRC_ATTACHED_TYPEC_ONLY_STATE: |
| /* |
| * Active TypeC only SRC connection. Because the connection is |
| * active and not a PD connection, apply the new Rp value now. |
| */ |
| atomic_set_bit(pdc->common_policy.flags, COMMON_POLICY_SET_RP); |
| __fallthrough; |
| case SRC_ATTACHED_STATE: |
| /* |
| * Active USB-PD SRC connection. Update the LPM source cap which |
| * will also trigger the PDC to send a new SRC_CAP message to |
| * the port partner. |
| */ |
| |
| /* Set flag to trigger SET_PDOS command to PDC */ |
| atomic_set_bit(pdc->src_policy.flags, |
| SRC_POLICY_UPDATE_SRC_CAPS); |
| break; |
| case SNK_ATTACHED_STATE: |
| /* |
| * Src policy can be set in a Snk connection to support an FRS |
| * partner. Update RP accordingly. |
| */ |
| atomic_set_bit(pdc->common_policy.flags, COMMON_POLICY_SET_RP); |
| __fallthrough; |
| case SNK_ATTACHED_TYPEC_ONLY_STATE: |
| /* Even when operating as a SNK, update the SRC caps |
| * so that the first PDO offered after a power role |
| * swap is a safe value. |
| */ |
| atomic_set_bit(pdc->snk_policy.flags, |
| SNK_POLICY_UPDATE_SRC_CAPS); |
| break; |
| case UNATTACHED_STATE: |
| /* Update the default Rp level */ |
| atomic_set_bit(pdc->una_policy.flags, UNA_POLICY_TCC); |
| |
| /* Set flag to trigger SET_PDOS command to PDC */ |
| atomic_set_bit(pdc->una_policy.flags, |
| UNA_POLICY_UPDATE_SRC_CAPS); |
| break; |
| } |
| |
| return EC_SUCCESS; |
| } |
| |
| bool pdc_power_mgmt_get_frs_hw_supported(int port) |
| { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| LOG_ERR("get_frs_hw_supported: invalid port %d", port); |
| return false; |
| } |
| return pdc_get_frs_supported(pdc_data[port]->port.pdc); |
| } |
| |
| test_mockable int pdc_power_mgmt_get_pch_data_status(int port, uint8_t *status) |
| { |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| if (status == NULL) { |
| return -EINVAL; |
| } |
| |
| /* Block until command completes */ |
| if (public_api_block(port, CMD_PDC_GET_PCH_DATA_STATUS)) { |
| /* something went wrong */ |
| return -EIO; |
| } |
| |
| memcpy(status, pdc_data[port]->port.pch_data_status, 5); |
| return 0; |
| } |
| |
| int pdc_power_mgmt_wait_for_sync(int port, int timeout_ms) |
| { |
| struct pdc_port_t *pdc; |
| int rv; |
| int ktime = (timeout_ms == -1 ? PDC_SM_SETTLED_TIMEOUT_MS : timeout_ms); |
| |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| pdc = &pdc_data[port]->port; |
| |
| /* Trigger re-scan of connector status. */ |
| atomic_set_bit(pdc->cci_flags, CCI_EVENT); |
| k_event_post(&pdc->sm_event, PDC_SM_EVENT); |
| |
| /* To avoid any race conditions, set reset arg to true to clear all |
| * events in settle_event, then wait for PDC_SM_SETTLED_EVENT to be |
| * posted. This is all handled under a lock within k_event_wait. */ |
| rv = k_event_wait(&pdc->settle_event, PDC_SM_SETTLED_EVENT, true, |
| K_MSEC(ktime)); |
| |
| if (!rv) { |
| return -ETIMEDOUT; |
| } |
| |
| k_event_clear(&pdc->settle_event, rv); |
| return 0; |
| } |
| |
| int pdc_power_mgmt_ppm_ack_status_change(int port, |
| union conn_status_change_bits_t ci) |
| { |
| struct pdc_port_t *pdc; |
| |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| pdc = &pdc_data[port]->port; |
| |
| pdc->overlay_ppm_changes.raw_value &= ~(ci.raw_value); |
| pdc->connector_status.raw_conn_status_change_bits &= ~(ci.raw_value); |
| |
| return 0; |
| } |
| |
| int pdc_power_mgmt_register_ppm_callback(const struct pdc_callback *callback) |
| { |
| struct pdc_port_t *pdc; |
| int port; |
| |
| /* The callback can safely be applied to all port structs, even inactive |
| * ones. Use the CONFIG_USB_PD_PORT_MAX_COUNT constant instead of |
| * pdc_power_mgmt_get_usb_pd_port_count() to make this function safe to |
| * call before initialization is complete. */ |
| for (port = 0; port < CONFIG_USB_PD_PORT_MAX_COUNT; ++port) { |
| pdc = &pdc_data[port]->port; |
| pdc->ppm_ci_cb = callback; |
| } |
| |
| return 0; |
| } |
| |
| int pdc_power_mgmt_register_board_callback(enum pdc_power_mgmt_board_cb_t type, |
| const void *callback) |
| { |
| struct pdc_port_t *pdc; |
| int port; |
| |
| /* The callback can safely be applied to all port structs, even inactive |
| * ones. Use the CONFIG_USB_PD_PORT_MAX_COUNT constant instead of |
| * pdc_power_mgmt_get_usb_pd_port_count() to make this function safe to |
| * call before initialization is complete. */ |
| for (port = 0; port < CONFIG_USB_PD_PORT_MAX_COUNT; ++port) { |
| pdc = &pdc_data[port]->port; |
| switch (type) { |
| case PDC_BOARD_CB_UNATTACH: |
| pdc->board_unattach_cb = |
| (pdc_power_mgmt_board_unattached_cb)callback; |
| break; |
| case PDC_BOARD_CB_DP_ATTENTION: |
| pdc->board_dp_attention_cb = |
| (pdc_power_mgmt_board_dp_attention_cb)callback; |
| break; |
| default: |
| break; |
| }; |
| } |
| |
| return 0; |
| } |
| |
| int pdc_power_mgmt_get_connector_status_for_ppm( |
| int port, union connector_status_t *connector_status) |
| { |
| struct pdc_port_t *pdc; |
| int rv; |
| |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| pdc = &pdc_data[port]->port; |
| |
| /* If a PPM event is pending, we need to wait for the connector status |
| * ready event to be set before continuing. |
| */ |
| if (atomic_test_bit(pdc->cci_flags, CCI_PPM_EVENT)) { |
| rv = k_event_wait(&pdc->settle_event, |
| PDC_PPM_CONNECTOR_STATUS_READY, false, |
| K_MSEC(PDC_SM_SETTLED_TIMEOUT_MS)); |
| |
| if (!rv) { |
| return -ETIMEDOUT; |
| } |
| |
| k_event_clear(&pdc->settle_event, rv); |
| } |
| |
| rv = pdc_power_mgmt_get_connector_status(port, connector_status); |
| |
| /* Overlay any additional connector status change bits we would like to |
| * add. This is necessary for the OPM to be made aware of role swaps and |
| * other methods causing connector status changes. |
| */ |
| if (rv == 0) { |
| connector_status->raw_conn_status_change_bits |= |
| pdc->overlay_ppm_changes.raw_value; |
| } |
| |
| return rv; |
| } |
| |
| #ifdef CONFIG_USBC_PDC_DRIVEN_CCD |
| int pdc_power_mgmt_get_ccd_port(void) |
| { |
| struct pdc_hw_config_t pdc_hw_config; |
| for (int i = 0; i < pdc_power_mgmt_get_usb_pd_port_count(); i++) { |
| const struct pdc_config_t *cfg = pdc_data[i]->port.dev->config; |
| pdc_power_mgmt_get_hw_config(i, &pdc_hw_config); |
| |
| if (pdc_hw_config.ccd) { |
| return cfg->connector_num; |
| } |
| } |
| |
| /* This is unreachable since compile-time checks in the PDC driver |
| * sources ensure that one port (per PDC type) is tagged as CCD when |
| * CONFIG_USBC_PDC_DRIVEN_CCD is enabled. */ |
| return -1; /* LCOV_EXCL_LINE */ |
| } |
| |
| test_mockable int pdc_power_mgmt_get_sbu_mux_mode(enum pdc_sbu_mux_mode *mode, |
| int *port_num) |
| { |
| if (mode == NULL) { |
| return -EINVAL; |
| } |
| |
| int port = pdc_power_mgmt_get_ccd_port(); |
| |
| __ASSERT(port >= 0, "No CCD port specified in devicetree"); |
| |
| if (port_num) { |
| *port_num = port; |
| } |
| |
| /* Block until command completes */ |
| if (public_api_block(port, CMD_PDC_GET_SBU_MUX_MODE)) { |
| return -EIO; |
| } |
| |
| *mode = pdc_data[port]->port.sbu_mux_mode; |
| return 0; |
| } |
| |
| test_mockable int pdc_power_mgmt_set_sbu_mux_mode(enum pdc_sbu_mux_mode mode) |
| { |
| int port = pdc_power_mgmt_get_ccd_port(); |
| |
| __ASSERT(port >= 0, "No CCD port specified in devicetree"); |
| |
| pdc_data[port]->port.sbu_mux_mode = mode; |
| |
| LOG_INF("C%d: Setting SBU mux mode to %d", port, mode); |
| |
| /* Block until command completes */ |
| return public_api_block(port, CMD_PDC_SET_SBU_MUX_MODE); |
| } |
| #endif /* defined(CONFIG_USBC_PDC_DRIVEN_CCD) */ |
| |
| test_mockable int pdc_power_mgmt_set_bbr_cts(int port, bool enable) |
| { |
| /* Make sure port is in range and that an output buffer is provided */ |
| if (!pdc_power_mgmt_is_pdc_port_valid(port)) { |
| return -ERANGE; |
| } |
| |
| pdc_data[port]->port.bbr_cts_enable = enable; |
| |
| /* Block until command completes */ |
| return public_api_block(port, CMD_PDC_SET_BBR_CTS); |
| } |
| |
| test_mockable int pdc_power_mgmt_set_ap_power_state(enum power_state state) |
| { |
| if (!(state == POWER_S0 || state == POWER_S5)) { |
| LOG_ERR("PD: Can only notify S0 and S5 states (state=%d)", |
| state); |
| return -EINVAL; |
| } |
| |
| for (int i = 0; i < pdc_power_mgmt_get_usb_pd_port_count(); i++) { |
| /* Issue command on all port instances */ |
| pdc_notify_ap_power_state(i, state); |
| } |
| |
| return 0; |
| } |
| |
| test_mockable void pdc_power_mgmt_simulate_power_button_press(int ms) |
| { |
| if (!IS_ENABLED(CONFIG_PLATFORM_EC_POWER_BUTTON)) |
| return; |
| |
| power_button_simulate_press(ms); |
| } |
| |
| #ifdef CONFIG_ZTEST |
| |
| bool test_pdc_power_mgmt_is_snk_typec_attached_run(int port) |
| { |
| __ASSERT(pdc_power_mgmt_is_pdc_port_valid(port), "Invalid USB-C port"); |
| |
| LOG_INF("RPZ SRC %d", |
| pdc_data[port]->port.snk_typec_attached_local_state); |
| return pdc_data[port]->port.snk_typec_attached_local_state == |
| SNK_TYPEC_ATTACHED_RUN; |
| } |
| |
| bool test_pdc_power_mgmt_is_src_typec_attached_run(int port) |
| { |
| __ASSERT(pdc_power_mgmt_is_pdc_port_valid(port), "Invalid USB-C port"); |
| |
| LOG_INF("RPZ SRC %d", |
| pdc_data[port]->port.src_typec_attached_local_state); |
| return pdc_data[port]->port.src_typec_attached_local_state == |
| SRC_TYPEC_ATTACHED_RUN; |
| } |
| |
| /* |
| * Reset the state machine for each port to its unattached state. This ensures |
| * that tests start from the same state and prevents commands from a previous |
| * test from impacting subsequently run tests. |
| */ |
| /* LCOV_EXCL_START */ |
| bool pdc_power_mgmt_test_wait_unattached(void) |
| { |
| int num_unattached; |
| |
| for (int port = 0; port < ARRAY_SIZE(pdc_data); port++) { |
| set_pdc_state(&pdc_data[port]->port, PDC_UNATTACHED); |
| } |
| |
| /* Wait for up to 20 * 100ms for all ports to become unattached. */ |
| for (int i = 0; i < 20; i++) { |
| k_msleep(100); |
| num_unattached = 0; |
| |
| for (int port = 0; port < ARRAY_SIZE(pdc_data); port++) { |
| if (pdc_data[port]->port.unattached_local_state == |
| UNATTACHED_RUN) { |
| num_unattached++; |
| } |
| } |
| |
| if (num_unattached == ARRAY_SIZE(pdc_data)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| /* LCOV_EXCL_STOP */ |
| |
| /* |
| * Ensure that the PDC attached state is either SRC_ATTACHED or SNK_ATTACHED and |
| * that the substate has reached the stead state for the attached state. |
| */ |
| /* LCOV_EXCL_START */ |
| bool pdc_power_mgmt_is_pd_attached(int port) |
| { |
| __ASSERT(pdc_power_mgmt_is_pdc_port_valid(port), "Invalid USB-C port"); |
| |
| if ((pdc_data[port]->port.attached_state == SNK_ATTACHED_STATE) && |
| (pdc_data[port]->port.snk_attached_local_state == |
| SNK_ATTACHED_RUN)) { |
| return true; |
| } |
| |
| if ((pdc_data[port]->port.attached_state == SRC_ATTACHED_STATE) && |
| (pdc_data[port]->port.src_attached_local_state == |
| SRC_ATTACHED_RUN)) { |
| return true; |
| } |
| |
| return false; |
| } |
| /* LCOV_EXCL_STOP */ |
| |
| #endif /* CONFIG_ZTEST */ |