blob: dbbaf29c6f53819e56202cd172914c8a22182db8 [file] [log] [blame]
/*
* Copyright (c) 2022 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);
#define PORT1_NODE DT_NODELABEL(port1)
#define PORT1_POWER_ROLE DT_ENUM_IDX(DT_NODELABEL(port1), power_role)
#if (PORT1_POWER_ROLE != TC_ROLE_SINK)
#error "Unsupported board: Only Sink device supported"
#endif
#define SINK_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 port1_data_t {
/** Sink Capabilities */
uint32_t snk_caps[DT_PROP_LEN(DT_NODELABEL(port1), sink_pdos)];
/** Number of Sink Capabilities */
int snk_cap_cnt;
/** Source Capabilities */
uint32_t src_caps[PDO_MAX_DATA_OBJECTS];
/** Number of Source Capabilities */
int src_cap_cnt;
/* Power Supply Ready flag */
atomic_t ps_ready;
} port1_data = {
.snk_caps = {DT_FOREACH_PROP_ELEM(DT_NODELABEL(port1), sink_pdos, SINK_PDO)},
.snk_cap_cnt = DT_PROP_LEN(DT_NODELABEL(port1), sink_pdos),
.src_caps = {0},
.src_cap_cnt = 0,
.ps_ready = 0
};
/* usbc.rst port data object end */
/**
* @brief Builds a Request Data Object (RDO) with the following properties:
* - Maximum operating current 100mA
* - Operating current is 100mA
* - Unchunked Extended Messages Not Supported
* - No USB Suspend
* - Not USB Communications Capable
* - No capability mismatch
* - Does not Giveback
* - Select object position 1 (5V Power Data Object (PDO))
*
* @note Generally a sink application would build an RDO from the
* Source Capabilities stored in the dpm_data object
*/
static uint32_t build_rdo(const struct port1_data_t *dpm_data)
{
union pd_rdo rdo;
/* Maximum operating current 100mA (GIVEBACK = 0) */
rdo.fixed.min_or_max_operating_current = PD_CONVERT_MA_TO_FIXED_PDO_CURRENT(100);
/* Operating current 100mA */
rdo.fixed.operating_current = PD_CONVERT_MA_TO_FIXED_PDO_CURRENT(100);
/* Unchunked Extended Messages Not Supported */
rdo.fixed.unchunked_ext_msg_supported = 0;
/* No USB Suspend */
rdo.fixed.no_usb_suspend = 1;
/* Not USB Communications Capable */
rdo.fixed.usb_comm_capable = 0;
/* No capability mismatch */
rdo.fixed.cap_mismatch = 0;
/* Don't giveback */
rdo.fixed.giveback = 0;
/* Object position 1 (5V PDO) */
rdo.fixed.object_pos = 1;
return rdo.raw_value;
}
static void display_pdo(const int idx,
const uint32_t pdo_value)
{
union pd_fixed_supply_pdo_source pdo;
/* Default to fixed supply pdo source until type is detected */
pdo.raw_value = pdo_value;
LOG_INF("PDO %d:", idx);
switch (pdo.type) {
case PDO_FIXED: {
LOG_INF("\tType: FIXED");
LOG_INF("\tCurrent: %d",
PD_CONVERT_FIXED_PDO_CURRENT_TO_MA(pdo.max_current));
LOG_INF("\tVoltage: %d",
PD_CONVERT_FIXED_PDO_VOLTAGE_TO_MV(pdo.voltage));
LOG_INF("\tPeak Current: %d", pdo.peak_current);
LOG_INF("\tUchunked Support: %d",
pdo.unchunked_ext_msg_supported);
LOG_INF("\tDual Role Data: %d",
pdo.dual_role_data);
LOG_INF("\tUSB Comms: %d",
pdo.usb_comms_capable);
LOG_INF("\tUnconstrained Pwr: %d",
pdo.unconstrained_power);
LOG_INF("\tUSB Suspend: %d",
pdo.usb_suspend_supported);
LOG_INF("\tDual Role Power: %d",
pdo.dual_role_power);
}
break;
case PDO_BATTERY: {
union pd_battery_supply_pdo_source pdo;
pdo.raw_value = pdo_value;
LOG_INF("\tType: BATTERY");
LOG_INF("\tMin Voltage: %d",
PD_CONVERT_BATTERY_PDO_VOLTAGE_TO_MV(pdo.min_voltage));
LOG_INF("\tMax Voltage: %d",
PD_CONVERT_BATTERY_PDO_VOLTAGE_TO_MV(pdo.max_voltage));
LOG_INF("\tMax Power: %d",
PD_CONVERT_BATTERY_PDO_POWER_TO_MW(pdo.max_power));
}
break;
case PDO_VARIABLE: {
union pd_variable_supply_pdo_source pdo;
pdo.raw_value = pdo_value;
LOG_INF("\tType: VARIABLE");
LOG_INF("\tMin Voltage: %d",
PD_CONVERT_VARIABLE_PDO_VOLTAGE_TO_MV(pdo.min_voltage));
LOG_INF("\tMax Voltage: %d",
PD_CONVERT_VARIABLE_PDO_VOLTAGE_TO_MV(pdo.max_voltage));
LOG_INF("\tMax Current: %d",
PD_CONVERT_VARIABLE_PDO_CURRENT_TO_MA(pdo.max_current));
}
break;
case PDO_AUGMENTED: {
union pd_augmented_supply_pdo_source pdo;
pdo.raw_value = pdo_value;
LOG_INF("\tType: AUGMENTED");
LOG_INF("\tMin Voltage: %d",
PD_CONVERT_AUGMENTED_PDO_VOLTAGE_TO_MV(pdo.min_voltage));
LOG_INF("\tMax Voltage: %d",
PD_CONVERT_AUGMENTED_PDO_VOLTAGE_TO_MV(pdo.max_voltage));
LOG_INF("\tMax Current: %d",
PD_CONVERT_AUGMENTED_PDO_CURRENT_TO_MA(pdo.max_current));
LOG_INF("\tPPS Power Limited: %d", pdo.pps_power_limited);
}
break;
}
}
static void display_source_caps(const struct device *dev)
{
struct port1_data_t *dpm_data = usbc_get_dpm_data(dev);
LOG_INF("Source Caps:");
for (int i = 0; i < dpm_data->src_cap_cnt; i++) {
display_pdo(i, dpm_data->src_caps[i]);
k_msleep(50);
}
}
/* usbc.rst callbacks start */
static int port1_policy_cb_get_snk_cap(const struct device *dev,
uint32_t **pdos,
int *num_pdos)
{
struct port1_data_t *dpm_data = usbc_get_dpm_data(dev);
*pdos = dpm_data->snk_caps;
num_pdos = &dpm_data->snk_cap_cnt;
return 0;
}
static void port1_policy_cb_set_src_cap(const struct device *dev,
const uint32_t *pdos,
const int num_pdos)
{
struct port1_data_t *dpm_data;
int num;
int i;
dpm_data = usbc_get_dpm_data(dev);
num = num_pdos;
if (num > PDO_MAX_DATA_OBJECTS) {
num = PDO_MAX_DATA_OBJECTS;
}
for (i = 0; i < num; i++) {
dpm_data->src_caps[i] = *(pdos + i);
}
dpm_data->src_cap_cnt = num;
}
static uint32_t port1_policy_cb_get_rdo(const struct device *dev)
{
struct port1_data_t *dpm_data = usbc_get_dpm_data(dev);
return build_rdo(dpm_data);
}
/* usbc.rst callbacks end */
/* usbc.rst notify start */
static void port1_notify(const struct device *dev,
const enum usbc_policy_notify_t policy_notify)
{
struct port1_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:
atomic_set_bit(&dpm_data->ps_ready, 0);
break;
case PD_CONNECTED:
break;
case NOT_PD_CONNECTED:
break;
case POWER_CHANGE_0A0:
LOG_INF("PWR 0A");
break;
case POWER_CHANGE_DEF:
LOG_INF("PWR DEF");
break;
case POWER_CHANGE_1A5:
LOG_INF("PWR 1A5");
break;
case POWER_CHANGE_3A0:
LOG_INF("PWR 3A0");
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 SNK_TRANSITION_TO_DEFAULT:
break;
case HARD_RESET_RECEIVED:
break;
}
}
/* usbc.rst notify end */
/* usbc.rst check start */
bool port1_policy_check(const struct device *dev,
const enum usbc_policy_check_t policy_check)
{
switch (policy_check) {
case CHECK_POWER_ROLE_SWAP:
/* Reject power role swaps */
return false;
case CHECK_DATA_ROLE_SWAP_TO_DFP:
/* Reject data role swap to DFP */
return false;
case CHECK_DATA_ROLE_SWAP_TO_UFP:
/* Accept data role swap to UFP */
return true;
case CHECK_SNK_AT_DEFAULT_LEVEL:
/* This device is always at the default power level */
return true;
default:
/* Reject all other policy checks */
return false;
}
}
/* usbc.rst check end */
void main(void)
{
const struct device *usbc_port1;
/* Get the device for this port */
usbc_port1 = DEVICE_DT_GET(PORT1_NODE);
if (!device_is_ready(usbc_port1)) {
LOG_ERR("PORT1 device not ready");
return;
}
/* usbc.rst register start */
/* Register USB-C Callbacks */
/* Register Policy Check callback */
usbc_set_policy_cb_check(usbc_port1, port1_policy_check);
/* Register Policy Notify callback */
usbc_set_policy_cb_notify(usbc_port1, port1_notify);
/* Register Policy Get Sink Capabilities callback */
usbc_set_policy_cb_get_snk_cap(usbc_port1, port1_policy_cb_get_snk_cap);
/* Register Policy Set Source Capabilities callback */
usbc_set_policy_cb_set_src_cap(usbc_port1, port1_policy_cb_set_src_cap);
/* Register Policy Get Request Data Object callback */
usbc_set_policy_cb_get_rdo(usbc_port1, port1_policy_cb_get_rdo);
/* usbc.rst register end */
/* usbc.rst user data start */
/* Set Application port data object. This object is passed to the policy callbacks */
port1_data.ps_ready = ATOMIC_INIT(0);
usbc_set_dpm_data(usbc_port1, &port1_data);
/* usbc.rst user data end */
/* usbc.rst usbc start */
/* Start the USB-C Subsystem */
usbc_start(usbc_port1);
/* usbc.rst usbc end */
while (1) {
/* Perform Application Specific functions */
if (atomic_test_and_clear_bit(&port1_data.ps_ready, 0)) {
/* Display the Source Capabilities */
display_source_caps(usbc_port1);
}
/* Arbitrary delay */
k_msleep(1000);
}
}