1# Copyright 2016 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import logging 6import math 7import time 8 9from autotest_lib.client.common_lib import error 10from autotest_lib.server.cros.faft.firmware_test import FirmwareTest 11from autotest_lib.server.cros.servo import pd_device 12 13 14class firmware_PDVbusRequest(FirmwareTest): 15 """ 16 Servo based USB PD VBUS level test. This test is written to use both 17 the DUT and PDTester test board. It requires that the DUT support 18 dualrole (SRC or SNK) operation. VBUS change requests occur in two 19 methods. 20 21 The 1st test initiates the VBUS change by using special PDTester 22 feature to send new SRC CAP message. This causes the DUT to request 23 a new VBUS voltage matching what's in the SRC CAP message. 24 25 The 2nd test configures the DUT in SNK mode and uses the pd console 26 command 'pd 0/1 dev V' command where V is the desired voltage 27 5/12/20. This test is more risky and won't be executed if the 1st 28 test is failed. If the DUT max input voltage is not 20V, like 12V, 29 and the FAFT config is set wrong, it may negotiate to a voltage 30 higher than it can support, that may damage the DUT. 31 32 Pass critera is all voltage transitions are successful. 33 34 """ 35 version = 1 36 PD_SETTLE_DELAY = 10 37 USBC_SINK_VOLTAGE = 5 38 VBUS_TOLERANCE = 0.12 39 40 VOLTAGE_SEQUENCE = [5, 9, 10, 12, 15, 20, 15, 12, 9, 5, 20, 41 5, 5, 9, 9, 10, 10, 12, 12, 15, 15, 20] 42 43 def _compare_vbus(self, expected_vbus_voltage, ok_to_fail): 44 """Check VBUS using pdtester 45 46 @param expected_vbus_voltage: nominal VBUS level (in volts) 47 @param ok_to_fail: True to not treat voltage-not-matched as failure. 48 49 @returns: a tuple containing pass/fail indication and logging string 50 """ 51 # Get Vbus voltage and current 52 vbus_voltage = self.pdtester.vbus_voltage 53 # Compute voltage tolerance range. To handle the case where VBUS is 54 # off, set the minimal tolerance to USBC_SINK_VOLTAGE * VBUS_TOLERANCE. 55 tolerance = (self.VBUS_TOLERANCE * max(expected_vbus_voltage, 56 self.USBC_SINK_VOLTAGE)) 57 voltage_difference = math.fabs(expected_vbus_voltage - vbus_voltage) 58 result_str = 'Target = %02dV:\tAct = %.2f\tDelta = %.2f' % \ 59 (expected_vbus_voltage, vbus_voltage, voltage_difference) 60 # Verify that measured Vbus voltage is within expected range 61 if voltage_difference > tolerance: 62 result = 'ALLOWED_FAIL' if ok_to_fail else 'FAIL' 63 else: 64 result = 'PASS' 65 return result, result_str 66 67 def initialize(self, host, cmdline_args, flip_cc=False, dts_mode=False, 68 init_power_mode=None): 69 super(firmware_PDVbusRequest, self).initialize(host, cmdline_args) 70 # Only run on DUTs that can supply battery power. 71 if not self._client.has_battery(): 72 raise error.TestNAError("DUT type does not have a battery.") 73 self.setup_pdtester(flip_cc, dts_mode) 74 # Only run in normal mode 75 self.switcher.setup_mode('normal') 76 if init_power_mode: 77 # Set the DUT to suspend or shutdown mode 78 self.set_ap_off_power_mode(init_power_mode) 79 self.usbpd.send_command('chan 0') 80 81 def cleanup(self): 82 # Set back to the max 20V SRC mode at the end. 83 self.pdtester.charge(self.pdtester.USBC_MAX_VOLTAGE) 84 85 self.usbpd.send_command('chan 0xffffffff') 86 self.restore_ap_on_power_mode() 87 super(firmware_PDVbusRequest, self).cleanup() 88 89 def run_once(self): 90 """Exectue VBUS request test. 91 92 """ 93 consoles = [self.usbpd, self.pdtester] 94 port_partner = pd_device.PDPortPartner(consoles) 95 96 # Identify a valid test port pair 97 port_pair = port_partner.identify_pd_devices() 98 if not port_pair: 99 raise error.TestFail('No PD connection found!') 100 101 for port in port_pair: 102 if port.is_pdtester: 103 self.pdtester_port = port 104 else: 105 self.dut_port = port 106 107 dut_connect_state = self.dut_port.get_pd_state() 108 logging.info('Initial DUT connect state = %s', dut_connect_state) 109 110 if not self.dut_port.is_connected(dut_connect_state): 111 raise error.TestFail("pd connection not found") 112 113 dut_voltage_limit = self.faft_config.usbc_input_voltage_limit 114 is_override = self.faft_config.charger_profile_override 115 if is_override: 116 logging.info('*** Custom charger profile takes over, which may ' 117 'cause voltage-not-matched. It is OK to fail. *** ') 118 119 pdtester_failures = [] 120 logging.info('Start PDTester initiated tests') 121 charging_voltages = self.pdtester.get_charging_voltages() 122 123 if dut_voltage_limit not in charging_voltages: 124 raise error.TestError('Plugged a wrong charger to servo v4? ' 125 '%dV not in supported voltages %s.' % 126 (dut_voltage_limit, str(charging_voltages))) 127 128 for voltage in charging_voltages: 129 logging.info('********* %r *********', voltage) 130 # Set charging voltage 131 self.pdtester.charge(voltage) 132 # Wait for new PD contract to be established 133 time.sleep(self.PD_SETTLE_DELAY) 134 # Get current PDTester PD state 135 pdtester_state = self.pdtester_port.get_pd_state() 136 # If PDTester is in SNK mode and the DUT is in S0, the DUT should 137 # source VBUS = USBC_SINK_VOLTAGE. If PDTester is in SNK mode, and 138 # the DUT is not in S0, the DUT shouldn't source VBUS, which means 139 # VBUS = 0. 140 if self.pdtester_port.is_snk(pdtester_state): 141 expected_vbus_voltage = (self.USBC_SINK_VOLTAGE 142 if self.get_power_state() == 'S0' else 0) 143 ok_to_fail = False 144 else: 145 expected_vbus_voltage = min(voltage, dut_voltage_limit) 146 ok_to_fail = is_override 147 148 result, result_str = self._compare_vbus(expected_vbus_voltage, 149 ok_to_fail) 150 logging.info('%s, %s', result_str, result) 151 if result == 'FAIL': 152 pdtester_failures.append(result_str) 153 154 # PDTester is set back to 20V SRC mode. 155 self.pdtester.charge(self.pdtester.USBC_MAX_VOLTAGE) 156 time.sleep(self.PD_SETTLE_DELAY) 157 158 if pdtester_failures: 159 logging.error('PDTester voltage source cap failures') 160 for fail in pdtester_failures: 161 logging.error('%s', fail) 162 number = len(pdtester_failures) 163 raise error.TestFail('PDTester failed %d times' % number) 164 165 # The DUT must be in SNK mode for the pd <port> dev <voltage> 166 # command to have an effect. 167 if not self.dut_port.is_snk(): 168 # DUT needs to be in SINK Mode, attempt to force change 169 self.dut_port.drp_set('snk') 170 time.sleep(self.PD_SETTLE_DELAY) 171 if not self.dut_port.is_snk(): 172 raise error.TestFail("DUT not able to connect in SINK mode") 173 174 logging.info('Start of DUT initiated tests') 175 dut_failures = [] 176 for v in self.VOLTAGE_SEQUENCE: 177 if v > dut_voltage_limit: 178 logging.info('Target = %02dV: skipped, over the limit %0dV', 179 v, dut_voltage_limit) 180 continue 181 if v not in charging_voltages: 182 logging.info('Target = %02dV: skipped, voltage unsupported, ' 183 'update hdctools and servo_v4 firmware', v) 184 continue 185 # Build 'pd <port> dev <voltage> command 186 cmd = 'pd %d dev %d' % (self.dut_port.port, v) 187 self.dut_port.utils.send_pd_command(cmd) 188 time.sleep(self.PD_SETTLE_DELAY) 189 result, result_str = self._compare_vbus(v, ok_to_fail=is_override) 190 logging.info('%s, %s', result_str, result) 191 if result == 'FAIL': 192 dut_failures.append(result_str) 193 194 # Make sure DUT is set back to its max voltage so DUT will accept all 195 # options 196 cmd = 'pd %d dev %d' % (self.dut_port.port, dut_voltage_limit) 197 self.dut_port.utils.send_pd_command(cmd) 198 time.sleep(self.PD_SETTLE_DELAY) 199 # The next group of tests need DUT to connect in SNK and SRC modes 200 self.dut_port.drp_set('on') 201 202 if dut_failures: 203 logging.error('DUT voltage request failures') 204 for fail in dut_failures: 205 logging.error('%s', fail) 206 number = len(dut_failures) 207 raise error.TestFail('DUT failed %d times' % number) 208