/* * Copyright (c) 2023 The Chromium OS Authors. * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG); #include "power_ctrl.h" #define USBC_PORT0_NODE DT_ALIAS(usbc_port0) #define USBC_PORT0_POWER_ROLE DT_ENUM_IDX(USBC_PORT0_NODE, power_role) /* A Source power role evauates to 1. See usbc_tc.h: TC_ROLE_SOURCE */ #if (USBC_PORT0_POWER_ROLE != 1) #error "Unsupported board: Only Source device supported" #endif #define SOURCE_PDO(node_id, prop, idx) (DT_PROP_BY_IDX(node_id, prop, idx)), /* usbc.rst port data object start */ /** * @brief A structure that encapsulates Port data. */ static struct port0_data_t { /** Source Capabilities */ uint32_t src_caps[DT_PROP_LEN(USBC_PORT0_NODE, source_pdos)]; /** Number of Source Capabilities */ int src_cap_cnt; /** CC Rp value */ int rp; /** Sink Request RDO */ union pd_rdo sink_request; /** Requested Object Pos */ int obj_pos; /** VCONN CC line*/ enum tc_cc_polarity vconn_pol; /** True if power supply is ready */ bool ps_ready; /** True if power supply should transition to a new level */ bool ps_tran_start; /** Log Sink Requested RDO to console */ atomic_t show_sink_request; } port0_data = { .rp = DT_ENUM_IDX(USBC_PORT0_NODE, typec_power_opmode), .src_caps = {DT_FOREACH_PROP_ELEM(USBC_PORT0_NODE, source_pdos, SOURCE_PDO)}, .src_cap_cnt = DT_PROP_LEN(USBC_PORT0_NODE, source_pdos), }; /* usbc.rst port data object end */ static void dump_sink_request_rdo(const uint32_t rdo) { union pd_rdo request; request.raw_value = rdo; LOG_INF("REQUEST RDO: %08x", rdo); LOG_INF("\tObject Position:\t %d", request.fixed.object_pos); LOG_INF("\tGiveback:\t\t %d", request.fixed.giveback); LOG_INF("\tCapability Mismatch:\t %d", request.fixed.cap_mismatch); LOG_INF("\tUSB Comm Capable:\t %d", request.fixed.usb_comm_capable); LOG_INF("\tNo USB Suspend:\t\t %d", request.fixed.no_usb_suspend); LOG_INF("\tUnchunk Ext MSG Support: %d", request.fixed.unchunked_ext_msg_supported); LOG_INF("\tOperating Current:\t %d mA", PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(request.fixed.operating_current)); if (request.fixed.giveback) { LOG_INF("\tMax Operating Current:\t %d mA", PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(request.fixed.min_or_max_operating_current)); } else { LOG_INF("\tMin Operating Current:\t %d mA", PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(request.fixed.min_or_max_operating_current)); } } /* usbc.rst callbacks start */ /** * @brief PE calls this function when it needs to set the Rp on CC */ int port0_policy_cb_get_src_rp(const struct device *dev, enum tc_rp_value *rp) { struct port0_data_t *dpm_data = usbc_get_dpm_data(dev); *rp = dpm_data->rp; return 0; } /** * @brief PE calls this function to Enable (5V) or Disable (0V) the * Power Supply */ int port0_policy_cb_src_en(const struct device *dev, bool en) { source_ctrl_set(en ? SOURCE_5V : SOURCE_0V); return 0; } /** * @brief PE calls this function to Enable or Disable VCONN */ int port0_policy_cb_vconn_en(const struct device *dev, enum tc_cc_polarity pol, bool en) { struct port0_data_t *dpm_data = usbc_get_dpm_data(dev); dpm_data->vconn_pol = pol; if (en == false) { /* Disable VCONN on CC1 and CC2 */ vconn_ctrl_set(VCONN_OFF); } else if (pol == TC_POLARITY_CC1) { /* set VCONN on CC1 */ vconn_ctrl_set(VCONN1_ON); } else { /* set VCONN on CC2 */ vconn_ctrl_set(VCONN2_ON); } return 0; } /** * @brief PE calls this function to get the Source Caps that will be sent * to the Sink */ int port0_policy_cb_get_src_caps(const struct device *dev, const uint32_t **pdos, uint32_t *num_pdos) { struct port0_data_t *dpm_data = usbc_get_dpm_data(dev); *pdos = dpm_data->src_caps; *num_pdos = dpm_data->src_cap_cnt; return 0; } /** * @brief PE calls this function to verify that a Sink's request if valid */ static enum usbc_snk_req_reply_t port0_policy_cb_check_sink_request(const struct device *dev, const uint32_t request_msg) { struct port0_data_t *dpm_data = usbc_get_dpm_data(dev); union pd_fixed_supply_pdo_source pdo; uint32_t obj_pos; uint32_t op_current; dpm_data->sink_request.raw_value = request_msg; obj_pos = dpm_data->sink_request.fixed.object_pos; op_current = PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(dpm_data->sink_request.fixed.operating_current); if (obj_pos == 0 || obj_pos > dpm_data->src_cap_cnt) { return SNK_REQUEST_REJECT; } pdo.raw_value = dpm_data->src_caps[obj_pos - 1]; if (dpm_data->sink_request.fixed.operating_current > pdo.max_current) { return SNK_REQUEST_REJECT; } dpm_data->obj_pos = obj_pos; atomic_set_bit(&port0_data.show_sink_request, 0); /* * Clear PS ready. This will be set to true after PS is ready after * it transitions to the new level. */ port0_data.ps_ready = false; return SNK_REQUEST_VALID; } /** * @brief PE calls this function to check if the Power Supply is at the requested * level */ static bool port0_policy_cb_is_ps_ready(const struct device *dev) { struct port0_data_t *dpm_data = usbc_get_dpm_data(dev); /* Return true to inform that the Power Supply is ready */ return dpm_data->ps_ready; } /** * @brief PE calls this function to check if the Present Contract is still * valid */ static bool port0_policy_cb_present_contract_is_valid(const struct device *dev, const uint32_t present_contract) { struct port0_data_t *dpm_data = usbc_get_dpm_data(dev); union pd_fixed_supply_pdo_source pdo; union pd_rdo request; uint32_t obj_pos; uint32_t op_current; request.raw_value = present_contract; obj_pos = request.fixed.object_pos; op_current = PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(request.fixed.operating_current); if (obj_pos == 0 || obj_pos > dpm_data->src_cap_cnt) { return false; } pdo.raw_value = dpm_data->src_caps[obj_pos - 1]; if (request.fixed.operating_current > pdo.max_current) { return false; } return true; } /* usbc.rst callbacks end */ /* usbc.rst notify start */ static void port0_notify(const struct device *dev, const enum usbc_policy_notify_t policy_notify) { struct port0_data_t *dpm_data = usbc_get_dpm_data(dev); switch (policy_notify) { case PROTOCOL_ERROR: break; case MSG_DISCARDED: break; case MSG_ACCEPT_RECEIVED: break; case MSG_REJECTED_RECEIVED: break; case MSG_NOT_SUPPORTED_RECEIVED: break; case TRANSITION_PS: dpm_data->ps_tran_start = true; break; case PD_CONNECTED: break; case NOT_PD_CONNECTED: break; case DATA_ROLE_IS_UFP: break; case DATA_ROLE_IS_DFP: break; case PORT_PARTNER_NOT_RESPONSIVE: LOG_INF("Port Partner not PD Capable"); break; case HARD_RESET_RECEIVED: /* * This notification is sent from the PE_SRC_Transition_to_default * state and requires the following: * 1: Vconn should be turned OFF * 2: Reset of the local hardware */ /* Power off VCONN */ vconn_ctrl_set(VCONN_OFF); /* Transition PS to Default level */ source_ctrl_set(SOURCE_5V); break; default: } } /* usbc.rst notify end */ /* usbc.rst check start */ bool port0_policy_check(const struct device *dev, const enum usbc_policy_check_t policy_check) { struct port0_data_t *dpm_data = usbc_get_dpm_data(dev); switch (policy_check) { case CHECK_POWER_ROLE_SWAP: /* Reject power role swaps */ return false; case CHECK_DATA_ROLE_SWAP_TO_DFP: /* Accept data role swap to DFP */ return true; case CHECK_DATA_ROLE_SWAP_TO_UFP: /* Reject data role swap to UFP */ return false; case CHECK_SRC_PS_AT_DEFAULT_LEVEL: /* * This check is sent from the PE_SRC_Transition_to_default * state and requires the following: * 1: Vconn should be turned ON * 2: Return TRUE when Power Supply is at default level */ /* Power on VCONN */ vconn_ctrl_set(dpm_data->vconn_pol); /* PS should be at default level after receiving a Hard Reset */ return true; default: /* Reject all other policy checks */ return false; } } /* usbc.rst check end */ int main(void) { const struct device *usbc_port0; /* Get the device for this port */ usbc_port0 = DEVICE_DT_GET(USBC_PORT0_NODE); if (!device_is_ready(usbc_port0)) { LOG_ERR("PORT0 device not ready"); return 0; } /* usbc.rst register start */ /* Register USB-C Callbacks */ /* Register Policy Check callback */ usbc_set_policy_cb_check(usbc_port0, port0_policy_check); /* Register Policy Notify callback */ usbc_set_policy_cb_notify(usbc_port0, port0_notify); /* Register Policy callback to set the Rp on CC lines */ usbc_set_policy_cb_get_src_rp(usbc_port0, port0_policy_cb_get_src_rp); /* Register Policy callback to enable or disable power supply */ usbc_set_policy_cb_src_en(usbc_port0, port0_policy_cb_src_en); /* Register Policy callback to enable or disable vconn */ usbc_set_vconn_control_cb(usbc_port0, port0_policy_cb_vconn_en); /* Register Policy callback to send the source caps to the sink */ usbc_set_policy_cb_get_src_caps(usbc_port0, port0_policy_cb_get_src_caps); /* Register Policy callback to check if the sink request is valid */ usbc_set_policy_cb_check_sink_request(usbc_port0, port0_policy_cb_check_sink_request); /* Register Policy callback to check if the power supply is ready */ usbc_set_policy_cb_is_ps_ready(usbc_port0, port0_policy_cb_is_ps_ready); /* Register Policy callback to check if Present Contract is still valid */ usbc_set_policy_cb_present_contract_is_valid(usbc_port0, port0_policy_cb_present_contract_is_valid); /* usbc.rst register end */ /* usbc.rst user data start */ /* Set Application port data object. This object is passed to the policy callbacks */ usbc_set_dpm_data(usbc_port0, &port0_data); /* usbc.rst user data end */ /* Flag to show sink request */ port0_data.show_sink_request = ATOMIC_INIT(0); /* Init power supply transition start */ port0_data.ps_tran_start = false; /* Init power supply ready */ port0_data.ps_ready = false; /* usbc.rst usbc start */ /* Start the USB-C Subsystem */ usbc_start(usbc_port0); /* usbc.rst usbc end */ while (1) { /* Perform Application Specific functions */ /* Transition PS to new level */ if (port0_data.ps_tran_start) { /* * Transition Power Supply to new voltage. * Okay if this blocks. */ source_ctrl_set(port0_data.obj_pos); port0_data.ps_ready = true; port0_data.ps_tran_start = false; } /* Display Sink Requests */ if (atomic_test_and_clear_bit(&port0_data.show_sink_request, 0)) { /* Display the Sink request */ dump_sink_request_rdo(port0_data.sink_request.raw_value); } /* Arbitrary delay */ k_msleep(10); } return 0; }