Aliases, including usbc_port0, start counting from zero. This meant that the DT alias is inconsistent with the port numbering in the samples. Change the numbering scheme to start from zero to make this consistent. Signed-off-by: Andreas Sandberg <andreas@sandberg.uk>
395 lines
10 KiB
C
395 lines
10 KiB
C
/*
|
|
* Copyright (c) 2023 The Chromium OS Authors.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/devicetree.h>
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/usb_c/usbc.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
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;
|
|
}
|