diff options
Diffstat (limited to 'zephyr/test/drivers/usbc_alt_mode/src/usbc_alt_mode.c')
-rw-r--r-- | zephyr/test/drivers/usbc_alt_mode/src/usbc_alt_mode.c | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/zephyr/test/drivers/usbc_alt_mode/src/usbc_alt_mode.c b/zephyr/test/drivers/usbc_alt_mode/src/usbc_alt_mode.c new file mode 100644 index 0000000000..a005e1de11 --- /dev/null +++ b/zephyr/test/drivers/usbc_alt_mode/src/usbc_alt_mode.c @@ -0,0 +1,445 @@ +/* Copyright 2022 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <stdint.h> +#include <zephyr/kernel.h> +#include <zephyr/ztest.h> +#include <zephyr/drivers/gpio/gpio_emul.h> + +#include "ec_commands.h" +#include "ec_tasks.h" +#include "emul/emul_isl923x.h" +#include "emul/tcpc/emul_ps8xxx.h" +#include "emul/tcpc/emul_tcpci.h" +#include "emul/tcpc/emul_tcpci_partner_snk.h" +#include "host_command.h" +#include "test/drivers/stubs.h" +#include "tcpm/tcpci.h" +#include "test/drivers/utils.h" +#include "test/drivers/test_state.h" + +#define TEST_PORT 0 + +/* Arbitrary */ +#define PARTNER_PRODUCT_ID 0x1234 +#define PARTNER_DEV_BINARY_CODED_DECIMAL 0x5678 + +BUILD_ASSERT(TEST_PORT == USBC_PORT_C0); + +struct usbc_alt_mode_fixture { + const struct emul *tcpci_emul; + const struct emul *charger_emul; + struct tcpci_partner_data partner; + struct tcpci_snk_emul_data snk_ext; +}; + +struct usbc_alt_mode_dp_unsupported_fixture { + const struct emul *tcpci_emul; + const struct emul *charger_emul; + struct tcpci_partner_data partner; + struct tcpci_snk_emul_data snk_ext; +}; + +static void connect_partner_to_port(struct usbc_alt_mode_fixture *fixture) +{ + const struct emul *tcpc_emul = fixture->tcpci_emul; + struct tcpci_partner_data *partner_emul = &fixture->partner; + + /* + * TODO(b/221439302) Updating the TCPCI emulator registers, updating the + * vbus, as well as alerting should all be a part of the connect + * function. + */ + /* Set VBUS to vSafe0V initially. */ + isl923x_emul_set_adc_vbus(fixture->charger_emul, 0); + tcpci_emul_set_reg(fixture->tcpci_emul, TCPC_REG_POWER_STATUS, + TCPC_REG_POWER_STATUS_VBUS_DET); + tcpci_emul_set_reg(fixture->tcpci_emul, TCPC_REG_EXT_STATUS, + TCPC_REG_EXT_STATUS_SAFE0V); + tcpci_tcpc_alert(0); + k_sleep(K_SECONDS(1)); + zassume_ok(tcpci_partner_connect_to_tcpci(partner_emul, tcpc_emul), + NULL); + + /* Wait for PD negotiation and current ramp. */ + k_sleep(K_SECONDS(10)); +} + +static void disconnect_partner_from_port(struct usbc_alt_mode_fixture *fixture) +{ + zassume_ok(tcpci_emul_disconnect_partner(fixture->tcpci_emul), NULL); + isl923x_emul_set_adc_vbus(fixture->charger_emul, 0); + k_sleep(K_SECONDS(1)); +} + +static void add_discovery_responses(struct tcpci_partner_data *partner) +{ + /* Add Discover Identity response */ + partner->identity_vdm[VDO_INDEX_HDR] = + VDO(USB_SID_PD, /* structured VDM */ true, + VDO_CMDT(CMDT_RSP_ACK) | CMD_DISCOVER_IDENT); + partner->identity_vdm[VDO_INDEX_IDH] = VDO_IDH( + /* USB host */ false, /* USB device */ false, IDH_PTYPE_AMA, + /* modal operation */ true, USB_VID_GOOGLE); + partner->identity_vdm[VDO_INDEX_CSTAT] = 0xabcdabcd; + partner->identity_vdm[VDO_INDEX_PRODUCT] = VDO_PRODUCT( + PARTNER_PRODUCT_ID, PARTNER_DEV_BINARY_CODED_DECIMAL); + /* Hardware version 1, firmware version 2 */ + partner->identity_vdm[VDO_INDEX_AMA] = 0x12000000; + partner->identity_vdos = VDO_INDEX_AMA + 1; + + /* Add Discover Modes response */ + /* Support one mode for DisplayPort VID. Copied from Hoho. */ + partner->modes_vdm[VDO_INDEX_HDR] = + VDO(USB_SID_DISPLAYPORT, /* structured VDM */ true, + VDO_CMDT(CMDT_RSP_ACK) | CMD_DISCOVER_MODES); + partner->modes_vdm[VDO_INDEX_HDR + 1] = VDO_MODE_DP( + 0, MODE_DP_PIN_C, 1, CABLE_PLUG, MODE_DP_V13, MODE_DP_SNK); + partner->modes_vdos = VDO_INDEX_HDR + 2; + + /* Add Discover SVIDs response */ + /* Support DisplayPort VID. */ + partner->svids_vdm[VDO_INDEX_HDR] = + VDO(USB_SID_PD, /* structured VDM */ true, + VDO_CMDT(CMDT_RSP_ACK) | CMD_DISCOVER_SVID); + partner->svids_vdm[VDO_INDEX_HDR + 1] = + VDO_SVID(USB_SID_DISPLAYPORT, 0); + partner->svids_vdos = VDO_INDEX_HDR + 2; +} + +static void add_displayport_mode_responses(struct tcpci_partner_data *partner) +{ + /* DisplayPort alt mode setup remains in the same suite as discovery + * setup because DisplayPort is picked from the Discovery VDOs offered. + */ + + /* Add DisplayPort EnterMode response */ + partner->enter_mode_vdm[VDO_INDEX_HDR] = + VDO(USB_SID_DISPLAYPORT, /* structured VDM */ true, + VDO_CMDT(CMDT_RSP_ACK) | CMD_ENTER_MODE); + partner->enter_mode_vdos = VDO_INDEX_HDR + 1; + + /* Add DisplayPort StatusUpdate response */ + partner->dp_status_vdm[VDO_INDEX_HDR] = + VDO(USB_SID_DISPLAYPORT, /* structured VDM */ true, + VDO_CMDT(CMDT_RSP_ACK) | CMD_DP_STATUS); + partner->dp_status_vdm[VDO_INDEX_HDR + 1] = + /* Mainly copied from hoho */ + VDO_DP_STATUS(0, /* IRQ_HPD */ + false, /* HPD_HI|LOW - Changed*/ + 0, /* request exit DP */ + 0, /* request exit USB */ + 0, /* MF pref */ + true, /* DP Enabled */ + 0, /* power low e.g. normal */ + 0x2 /* Connected as Sink */); + partner->dp_status_vdos = VDO_INDEX_HDR + 2; + + /* Add DisplayPort Configure Response */ + partner->dp_config_vdm[VDO_INDEX_HDR] = + VDO(USB_SID_DISPLAYPORT, /* structured VDM */ true, + VDO_CMDT(CMDT_RSP_ACK) | CMD_DP_CONFIG); + partner->dp_config_vdos = VDO_INDEX_HDR + 1; +} + +static void *usbc_alt_mode_setup(void) +{ + static struct usbc_alt_mode_fixture fixture; + struct tcpci_partner_data *partner = &fixture.partner; + struct tcpci_snk_emul_data *snk_ext = &fixture.snk_ext; + + tcpci_partner_init(partner, PD_REV20); + partner->extensions = tcpci_snk_emul_init(snk_ext, partner, NULL); + + /* Get references for the emulators */ + fixture.tcpci_emul = EMUL_GET_USBC_BINDING(TEST_PORT, tcpc); + fixture.charger_emul = EMUL_GET_USBC_BINDING(TEST_PORT, chg); + + add_discovery_responses(partner); + add_displayport_mode_responses(partner); + + /* Sink 5V 3A. */ + snk_ext->pdo[1] = PDO_FIXED(5000, 3000, PDO_FIXED_UNCONSTRAINED); + + return &fixture; +} + +static void *usbc_alt_mode_dp_unsupported_setup(void) +{ + static struct usbc_alt_mode_fixture fixture; + struct tcpci_partner_data *partner = &fixture.partner; + struct tcpci_snk_emul_data *snk_ext = &fixture.snk_ext; + + tcpci_partner_init(partner, PD_REV20); + partner->extensions = tcpci_snk_emul_init(snk_ext, partner, NULL); + + /* Get references for the emulators */ + fixture.tcpci_emul = EMUL_GET_USBC_BINDING(TEST_PORT, tcpc); + /* The configured TCPCI rev must match the emulator's supported rev. */ + tcpc_config[TEST_PORT].flags |= TCPC_FLAGS_TCPCI_REV2_0; + tcpci_emul_set_rev(fixture.tcpci_emul, TCPCI_EMUL_REV2_0_VER1_1); + fixture.charger_emul = EMUL_GET_USBC_BINDING(TEST_PORT, chg); + + /* + * Respond to discovery REQs to indicate DisplayPort support, but do not + * respond to DisplayPort alt mode VDMs, including Enter Mode. + */ + add_discovery_responses(partner); + + /* Sink 5V 3A. */ + snk_ext->pdo[1] = PDO_FIXED(5000, 3000, PDO_FIXED_UNCONSTRAINED); + + return &fixture; +} + +static void usbc_alt_mode_before(void *data) +{ + /* Set chipset to ON, this will set TCPM to DRP */ + test_set_chipset_to_s0(); + + /* TODO(b/214401892): Check why need to give time TCPM to spin */ + k_sleep(K_SECONDS(1)); + + connect_partner_to_port((struct usbc_alt_mode_fixture *)data); +} + +static void usbc_alt_mode_after(void *data) +{ + disconnect_partner_from_port((struct usbc_alt_mode_fixture *)data); +} + +ZTEST_F(usbc_alt_mode, verify_discovery) +{ + uint8_t response_buffer[EC_LPC_HOST_PACKET_SIZE]; + struct ec_response_typec_discovery *discovery = + (struct ec_response_typec_discovery *)response_buffer; + host_cmd_typec_discovery(TEST_PORT, TYPEC_PARTNER_SOP, response_buffer, + sizeof(response_buffer)); + + /* The host command does not count the VDM header in identity_count. */ + zassert_equal(discovery->identity_count, + fixture->partner.identity_vdos - 1, + "Expected %d identity VDOs, got %d", + fixture->partner.identity_vdos - 1, + discovery->identity_count); + zassert_mem_equal( + discovery->discovery_vdo, fixture->partner.identity_vdm + 1, + discovery->identity_count * sizeof(*discovery->discovery_vdo), + "Discovered SOP identity ACK did not match"); + zassert_equal(discovery->svid_count, 1, "Expected 1 SVID, got %d", + discovery->svid_count); + zassert_equal(discovery->svids[0].svid, USB_SID_DISPLAYPORT, + "Expected SVID 0x%0000x, got 0x%0000x", + USB_SID_DISPLAYPORT, discovery->svids[0].svid); + zassert_equal(discovery->svids[0].mode_count, 1, + "Expected 1 DP mode, got %d", + discovery->svids[0].mode_count); + zassert_equal(discovery->svids[0].mode_vdo[0], + fixture->partner.modes_vdm[1], + "DP mode VDOs did not match"); +} + +ZTEST_F(usbc_alt_mode, verify_displayport_mode_entry) +{ + if (IS_ENABLED(CONFIG_PLATFORM_EC_USB_PD_REQUIRE_AP_MODE_ENTRY)) { + host_cmd_typec_control_enter_mode(TEST_PORT, TYPEC_MODE_DP); + k_sleep(K_SECONDS(1)); + } + + /* Verify host command when VDOs are present. */ + struct ec_response_typec_status status; + struct ec_params_usb_pd_get_mode_response response; + int response_size; + + host_cmd_usb_pd_get_amode(TEST_PORT, 0, &response, &response_size); + + /* Response should be populated with a DisplayPort VDO */ + zassert_equal(response_size, sizeof(response), NULL); + zassert_equal(response.svid, USB_SID_DISPLAYPORT, NULL); + zassert_equal(response.vdo[0], + fixture->partner.modes_vdm[response.opos], NULL); + + /* DPM configures the partner on DP mode entry */ + /* Verify port partner thinks its configured for DisplayPort */ + zassert_true(fixture->partner.displayport_configured, NULL); + /* Verify we also set up DP on our mux */ + status = host_cmd_typec_status(TEST_PORT); + zassert_equal((status.mux_state & USB_PD_MUX_DP_ENABLED), + USB_PD_MUX_DP_ENABLED, "Failed to see DP set in mux"); + + /* + * DP alt mode partner sends HPD through VDM:Attention, which uses the + * same format as the DP Status data + */ + uint32_t vdm_attention_data[2]; + + vdm_attention_data[0] = + VDO(USB_SID_DISPLAYPORT, 1, + VDO_OPOS(1) | VDO_CMDT(CMDT_INIT) | CMD_ATTENTION); + vdm_attention_data[1] = VDO_DP_STATUS(1, /* IRQ_HPD */ + true, /* HPD_HI|LOW - Changed*/ + 0, /* request exit DP */ + 0, /* request exit USB */ + 0, /* MF pref */ + true, /* DP Enabled */ + 0, /* power low e.g. normal */ + 0x2 /* Connected as Sink */); + tcpci_partner_send_data_msg(&fixture->partner, PD_DATA_VENDOR_DEF, + vdm_attention_data, 2, 0); + + k_sleep(K_SECONDS(1)); + /* Verify the board's HPD notification triggered */ + status = host_cmd_typec_status(TEST_PORT); + zassert_equal((status.mux_state & USB_PD_MUX_HPD_LVL), + USB_PD_MUX_HPD_LVL, "Failed to set HPD level in mux"); + zassert_equal((status.mux_state & USB_PD_MUX_HPD_IRQ), + USB_PD_MUX_HPD_IRQ, "Failed to set HPD IRQin mux"); +} + +ZTEST_F(usbc_alt_mode, verify_displayport_mode_reentry) +{ + if (!IS_ENABLED(CONFIG_PLATFORM_EC_USB_PD_REQUIRE_AP_MODE_ENTRY)) { + ztest_test_skip(); + } + + host_cmd_typec_control_enter_mode(TEST_PORT, TYPEC_MODE_DP); + k_sleep(K_SECONDS(1)); + + /* DPM configures the partner on DP mode entry */ + /* Verify port partner thinks its configured for DisplayPort */ + zassert_true(fixture->partner.displayport_configured, NULL); + + host_cmd_typec_control_exit_modes(TEST_PORT); + k_sleep(K_SECONDS(1)); + zassert_false(fixture->partner.displayport_configured, NULL); + + host_cmd_typec_control_enter_mode(TEST_PORT, TYPEC_MODE_DP); + k_sleep(K_SECONDS(1)); + zassert_true(fixture->partner.displayport_configured, NULL); + + /* Verify that DisplayPort is the active alternate mode. */ + struct ec_params_usb_pd_get_mode_response response; + int response_size; + + host_cmd_usb_pd_get_amode(TEST_PORT, 0, &response, &response_size); + + /* Response should be populated with a DisplayPort VDO */ + zassert_equal(response_size, sizeof(response), NULL); + zassert_equal(response.svid, USB_SID_DISPLAYPORT, NULL); + zassert_equal(response.vdo[0], + fixture->partner.modes_vdm[response.opos], NULL); +} + +ZTEST_F(usbc_alt_mode, verify_discovery_via_pd_host_cmd) +{ + struct ec_params_usb_pd_info_request params = { .port = TEST_PORT }; + struct ec_params_usb_pd_discovery_entry response; + + struct host_cmd_handler_args args = BUILD_HOST_COMMAND( + EC_CMD_USB_PD_DISCOVERY, 0, response, params); + + zassert_ok(host_command_process(&args)); + zassert_equal(args.response_size, sizeof(response), NULL); + zassert_equal(response.ptype, IDH_PTYPE_AMA); + zassert_equal(response.vid, USB_VID_GOOGLE); + zassert_equal(response.pid, PARTNER_PRODUCT_ID); +} + +ZTEST_F(usbc_alt_mode, verify_mode_entry_via_pd_host_cmd) +{ + if (!IS_ENABLED(CONFIG_PLATFORM_EC_USB_PD_REQUIRE_AP_MODE_ENTRY)) { + ztest_test_skip(); + } + + /* Verify entering mode */ + struct ec_params_usb_pd_set_mode_request set_mode_params = { + .cmd = PD_ENTER_MODE, + .port = TEST_PORT, + .opos = 1, /* Second VDO (after Discovery Responses) */ + .svid = USB_SID_DISPLAYPORT, + }; + + struct host_cmd_handler_args set_mode_args = BUILD_HOST_COMMAND_PARAMS( + EC_CMD_USB_PD_SET_AMODE, 0, set_mode_params); + + zassert_ok(host_command_process(&set_mode_args)); + + /* Verify that DisplayPort is the active alternate mode. */ + struct ec_params_usb_pd_get_mode_response get_mode_response; + int response_size; + + host_cmd_usb_pd_get_amode(TEST_PORT, 0, &get_mode_response, + &response_size); + + /* Response should be populated with a DisplayPort VDO */ + zassert_equal(response_size, sizeof(get_mode_response), NULL); + zassert_equal(get_mode_response.svid, USB_SID_DISPLAYPORT, NULL); + zassert_equal(get_mode_response.vdo[0], + fixture->partner.modes_vdm[get_mode_response.opos], NULL); +} + +ZTEST_SUITE(usbc_alt_mode, drivers_predicate_post_main, usbc_alt_mode_setup, + usbc_alt_mode_before, usbc_alt_mode_after, NULL); + +/* + * When the partner advertises DP mode support but refuses to enter, discovery + * should still work as if the partner were compliant. + */ +ZTEST_F(usbc_alt_mode_dp_unsupported, verify_discovery) +{ + if (IS_ENABLED(CONFIG_PLATFORM_EC_USB_PD_REQUIRE_AP_MODE_ENTRY)) { + host_cmd_typec_control_enter_mode(TEST_PORT, TYPEC_MODE_DP); + k_sleep(K_SECONDS(1)); + } + + uint8_t response_buffer[EC_LPC_HOST_PACKET_SIZE]; + struct ec_response_typec_discovery *discovery = + (struct ec_response_typec_discovery *)response_buffer; + host_cmd_typec_discovery(TEST_PORT, TYPEC_PARTNER_SOP, response_buffer, + sizeof(response_buffer)); + + /* The host command does not count the VDM header in identity_count. */ + zassert_equal(discovery->identity_count, + fixture->partner.identity_vdos - 1, + "Expected %d identity VDOs, got %d", + fixture->partner.identity_vdos - 1, + discovery->identity_count); + zassert_mem_equal( + discovery->discovery_vdo, fixture->partner.identity_vdm + 1, + discovery->identity_count * sizeof(*discovery->discovery_vdo), + "Discovered SOP identity ACK did not match"); + zassert_equal(discovery->svid_count, 1, "Expected 1 SVID, got %d", + discovery->svid_count); + zassert_equal(discovery->svids[0].svid, USB_SID_DISPLAYPORT, + "Expected SVID 0x%0000x, got 0x%0000x", + USB_SID_DISPLAYPORT, discovery->svids[0].svid); + zassert_equal(discovery->svids[0].mode_count, 1, + "Expected 1 DP mode, got %d", + discovery->svids[0].mode_count); + zassert_equal(discovery->svids[0].mode_vdo[0], + fixture->partner.modes_vdm[1], + "DP mode VDOs did not match"); +} + +/* + * When the partner advertises DP support but refuses to enter DP mode, the TCPM + * should try once and then give up. + */ +ZTEST_F(usbc_alt_mode_dp_unsupported, verify_displayport_mode_nonentry) +{ + if (IS_ENABLED(CONFIG_PLATFORM_EC_USB_PD_REQUIRE_AP_MODE_ENTRY)) { + host_cmd_typec_control_enter_mode(TEST_PORT, TYPEC_MODE_DP); + k_sleep(K_SECONDS(1)); + } + + zassert_false(fixture->partner.displayport_configured, NULL); + int dp_attempts = atomic_get(&fixture->partner.mode_enter_attempts); + zassert_equal(dp_attempts, 1, "Expected 1 DP attempt, got %d", + dp_attempts); +} + +ZTEST_SUITE(usbc_alt_mode_dp_unsupported, drivers_predicate_post_main, + usbc_alt_mode_dp_unsupported_setup, usbc_alt_mode_before, + usbc_alt_mode_after, NULL); |