summaryrefslogtreecommitdiff
path: root/zephyr/emul/tcpc/emul_tcpci_partner_src.c
diff options
context:
space:
mode:
Diffstat (limited to 'zephyr/emul/tcpc/emul_tcpci_partner_src.c')
-rw-r--r--zephyr/emul/tcpc/emul_tcpci_partner_src.c279
1 files changed, 279 insertions, 0 deletions
diff --git a/zephyr/emul/tcpc/emul_tcpci_partner_src.c b/zephyr/emul/tcpc/emul_tcpci_partner_src.c
new file mode 100644
index 0000000000..8dad1c9c5e
--- /dev/null
+++ b/zephyr/emul/tcpc/emul_tcpci_partner_src.c
@@ -0,0 +1,279 @@
+/* Copyright 2021 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(tcpci_src_emul, CONFIG_TCPCI_EMUL_LOG_LEVEL);
+
+#include <zephyr.h>
+
+#include "common.h"
+#include "emul/tcpc/emul_tcpci_partner_common.h"
+#include "emul/tcpc/emul_tcpci_partner_src.h"
+#include "emul/tcpc/emul_tcpci.h"
+#include "usb_pd.h"
+
+/**
+ * @brief Send capability message constructed from source device emulator PDOs
+ *
+ * @param data Pointer to USB-C source device emulator
+ * @param delay Optional delay
+ *
+ * @return 0 on success
+ * @return -ENOMEM when there is no free memory for message
+ * @return -EINVAL on TCPCI emulator add RX message error
+ */
+static int tcpci_src_emul_send_capability_msg(struct tcpci_src_emul_data *data,
+ uint64_t delay)
+{
+ int pdos;
+
+ /* Find number of PDOs */
+ for (pdos = 0; pdos < PDO_MAX_OBJECTS; pdos++) {
+ if (data->pdo[pdos] == 0) {
+ break;
+ }
+ }
+
+ return tcpci_partner_send_data_msg(&data->common_data,
+ PD_DATA_SOURCE_CAP,
+ data->pdo, pdos, delay);
+}
+
+/**
+ * @brief Function called when TCPM wants to transmit message. Accept received
+ * message and generate response.
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param ops Pointer to partner operations structure
+ * @param tx_msg Pointer to TX message buffer
+ * @param type Type of message
+ * @param retry Count of retries
+ */
+static void tcpci_src_emul_transmit_op(const struct emul *emul,
+ const struct tcpci_emul_partner_ops *ops,
+ const struct tcpci_emul_msg *tx_msg,
+ enum tcpci_msg_type type,
+ int retry)
+{
+ struct tcpci_src_emul_data *data =
+ CONTAINER_OF(ops, struct tcpci_src_emul_data, ops);
+ uint16_t header;
+
+ /* Acknowledge that message was sent successfully */
+ tcpci_emul_partner_msg_status(emul, TCPCI_EMUL_TX_SUCCESS);
+
+ /* Handle only SOP messages */
+ if (type != TCPCI_MSG_SOP) {
+ return;
+ }
+
+ LOG_HEXDUMP_DBG(tx_msg->buf, tx_msg->cnt, "Source received message");
+
+ header = (tx_msg->buf[1] << 8) | tx_msg->buf[0];
+
+ if (PD_HEADER_CNT(header)) {
+ /* Handle data message */
+ switch (PD_HEADER_TYPE(header)) {
+ case PD_DATA_REQUEST:
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_ACCEPT, 0);
+ /* PS ready after 15 ms */
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_PS_RDY, 15);
+ break;
+ case PD_DATA_VENDOR_DEF:
+ /* VDM (vendor defined message) - ignore */
+ break;
+ default:
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_REJECT, 0);
+ break;
+ }
+ } else {
+ /* Handle control message */
+ switch (PD_HEADER_TYPE(header)) {
+ case PD_CTRL_GET_SOURCE_CAP:
+ tcpci_src_emul_send_capability_msg(data, 0);
+ break;
+ case PD_CTRL_GET_SINK_CAP:
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_REJECT, 0);
+ break;
+ case PD_CTRL_DR_SWAP:
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_REJECT, 0);
+ break;
+ case PD_CTRL_SOFT_RESET:
+ data->common_data.msg_id = 0;
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_ACCEPT, 0);
+ /* Send capability after 15 ms to establish PD again */
+ tcpci_src_emul_send_capability_msg(data, 15);
+ break;
+ default:
+ tcpci_partner_send_control_msg(&data->common_data,
+ PD_CTRL_REJECT, 0);
+ break;
+ }
+ }
+}
+
+/**
+ * @brief Function called when TCPM consumes message. Free message that is no
+ * longer needed.
+ *
+ * @param emul Pointer to TCPCI emulator
+ * @param ops Pointer to partner operations structure
+ * @param rx_msg Message that was consumed by TCPM
+ */
+static void tcpci_src_emul_rx_consumed_op(
+ const struct emul *emul,
+ const struct tcpci_emul_partner_ops *ops,
+ const struct tcpci_emul_msg *rx_msg)
+{
+ struct tcpci_partner_msg *msg = CONTAINER_OF(rx_msg,
+ struct tcpci_partner_msg,
+ msg);
+
+ tcpci_partner_free_msg(msg);
+}
+
+/** Check description in emul_tcpci_partner_src.h */
+int tcpci_src_emul_connect_to_tcpci(struct tcpci_src_emul_data *data,
+ const struct emul *tcpci_emul)
+{
+ int ec;
+
+ tcpci_emul_set_partner_ops(tcpci_emul, &data->ops);
+ ec = tcpci_emul_connect_partner(tcpci_emul, PD_ROLE_SOURCE,
+ TYPEC_CC_VOLT_RP_3_0,
+ TYPEC_CC_VOLT_OPEN, POLARITY_CC1);
+ if (ec) {
+ return ec;
+ }
+
+ data->common_data.tcpci_emul = tcpci_emul;
+
+ return tcpci_src_emul_send_capability_msg(data, 0);
+}
+
+#define PDO_FIXED_FLAGS_MASK \
+ (PDO_FIXED_DUAL_ROLE | PDO_FIXED_UNCONSTRAINED | \
+ PDO_FIXED_COMM_CAP | PDO_FIXED_DATA_SWAP)
+
+/** Check description in emul_tcpci_parnter_src.h */
+enum check_pdos_res tcpci_src_emul_check_pdos(struct tcpci_src_emul_data *data)
+{
+ int volt_i_min;
+ int volt_i_max;
+ int volt_min;
+ int volt_max;
+ int i;
+
+ /* Check that first PDO is fixed 5V */
+ if ((data->pdo[0] & PDO_TYPE_MASK) != PDO_TYPE_FIXED ||
+ PDO_FIXED_VOLTAGE(data->pdo[0]) != 5000) {
+ return TCPCI_SRC_EMUL_FIRST_PDO_NO_FIXED_5V;
+ }
+
+ /* Check fixed PDOs are before other types and are in correct order */
+ for (i = 1, volt_min = -1;
+ i < PDO_MAX_OBJECTS && data->pdo[i] != 0 &&
+ (data->pdo[i] & PDO_TYPE_MASK) != PDO_TYPE_FIXED;
+ i++) {
+ volt_i_min = PDO_FIXED_VOLTAGE(data->pdo[i]);
+ /* Each voltage should be only once */
+ if (volt_i_min == volt_min || volt_i_min == 5000) {
+ return TCPCI_SRC_EMUL_FIXED_VOLT_REPEATED;
+ }
+ /* Check that voltage is increasing in next PDO */
+ if (volt_i_min < volt_min) {
+ return TCPCI_SRC_EMUL_FIXED_VOLT_NOT_IN_ORDER;
+ }
+ /* Check that fixed PDOs (except first) have cleared flags */
+ if (data->pdo[i] & PDO_FIXED_FLAGS_MASK) {
+ return TCPCI_SRC_EMUL_NON_FIRST_PDO_FIXED_FLAGS;
+ }
+ /* Save current voltage */
+ volt_min = volt_i_min;
+ }
+
+ /* Check battery PDOs are before variable type and are in order */
+ for (volt_min = -1, volt_max = -1;
+ i < PDO_MAX_OBJECTS && data->pdo[i] != 0 &&
+ (data->pdo[i] & PDO_TYPE_MASK) != PDO_TYPE_BATTERY;
+ i++) {
+ volt_i_min = PDO_BATT_MIN_VOLTAGE(data->pdo[i]);
+ volt_i_max = PDO_BATT_MAX_VOLTAGE(data->pdo[i]);
+ /* Each voltage range should be only once */
+ if (volt_i_min == volt_min && volt_i_max == volt_max) {
+ return TCPCI_SRC_EMUL_BATT_VOLT_REPEATED;
+ }
+ /*
+ * Lower minimal voltage should be first, than lower maximal
+ * voltage.
+ */
+ if (volt_i_min < volt_min ||
+ (volt_i_min == volt_min && volt_i_max < volt_max)) {
+ return TCPCI_SRC_EMUL_BATT_VOLT_NOT_IN_ORDER;
+ }
+ /* Save current voltage */
+ volt_min = volt_i_min;
+ volt_max = volt_i_max;
+ }
+
+ /* Check variable PDOs are last and are in correct order */
+ for (volt_min = -1, volt_max = -1;
+ i < PDO_MAX_OBJECTS && data->pdo[i] != 0 &&
+ (data->pdo[i] & PDO_TYPE_MASK) != PDO_TYPE_VARIABLE;
+ i++) {
+ volt_i_min = PDO_VAR_MIN_VOLTAGE(data->pdo[i]);
+ volt_i_max = PDO_VAR_MAX_VOLTAGE(data->pdo[i]);
+ /* Each voltage range should be only once */
+ if (volt_i_min == volt_min && volt_i_max == volt_max) {
+ return TCPCI_SRC_EMUL_VAR_VOLT_REPEATED;
+ }
+ /*
+ * Lower minimal voltage should be first, than lower maximal
+ * voltage.
+ */
+ if (volt_i_min < volt_min ||
+ (volt_i_min == volt_min && volt_i_max < volt_max)) {
+ return TCPCI_SRC_EMUL_VAR_VOLT_NOT_IN_ORDER;
+ }
+ /* Save current voltage */
+ volt_min = volt_i_min;
+ volt_max = volt_i_max;
+ }
+
+ /* Check that all PDOs after first 0 are unused and set to 0 */
+ for (; i < PDO_MAX_OBJECTS; i++) {
+ if (data->pdo[i] != 0) {
+ return TCPCI_SRC_EMUL_PDO_AFTER_ZERO;
+ }
+ }
+
+ return TCPCI_SRC_EMUL_CHECK_PDO_OK;
+}
+
+/** Check description in emul_tcpci_parnter_src.h */
+void tcpci_src_emul_init(struct tcpci_src_emul_data *data)
+{
+ tcpci_partner_init(&data->common_data);
+
+ data->common_data.data_role = PD_ROLE_UFP;
+ data->common_data.power_role = PD_ROLE_SOURCE;
+ data->common_data.rev = PD_REV20;
+
+ data->ops.transmit = tcpci_src_emul_transmit_op;
+ data->ops.rx_consumed = tcpci_src_emul_rx_consumed_op;
+ data->ops.control_change = NULL;
+
+ /* By default there is only PDO 5v@3A */
+ data->pdo[0] = PDO_FIXED(5000, 3000, PDO_FIXED_UNCONSTRAINED);
+ for (int i = 1; i < PDO_MAX_OBJECTS; i++) {
+ data->pdo[i] = 0;
+ }
+}