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 _is_batt_full(self): 68 """Check if battery is full 69 70 @returns: True if battery is full, False otherwise 71 """ 72 self.ec.update_battery_info() 73 return not self.ec.get_battery_charging_allowed(print_result=False) 74 75 def _enable_dps(self, en): 76 """Enable/disable Dynamic PDO Selection 77 78 @param en: a bool, True for enable, disable otherwise. 79 80 """ 81 self.usbpd.send_command('dps %s' % ('en' if en else 'dis')) 82 83 def initialize(self, host, cmdline_args, flip_cc=False, dts_mode=False, 84 init_power_mode=None): 85 super(firmware_PDVbusRequest, self).initialize(host, cmdline_args) 86 # Only run on DUTs that can supply battery power. 87 if not self._client.has_battery(): 88 raise error.TestNAError("DUT type does not have a battery.") 89 self.setup_pdtester(flip_cc, dts_mode) 90 # Only run in normal mode 91 self.switcher.setup_mode('normal') 92 93 self.shutdown_power_mode = False 94 if init_power_mode: 95 # Set the DUT to suspend or shutdown mode 96 self.set_ap_off_power_mode(init_power_mode) 97 if init_power_mode == "shutdown": 98 self.shutdown_power_mode = True 99 100 self.usbpd.send_command('chan 0') 101 logging.info('Disallow PR_SWAP request from DUT') 102 self.pdtester.allow_pr_swap(False) 103 # Disable dynamic PDO selection for voltage testing 104 self._enable_dps(False) 105 106 def cleanup(self): 107 logging.info('Allow PR_SWAP request from DUT') 108 self.pdtester.allow_pr_swap(True) 109 # Re-enable DPS 110 self._enable_dps(True) 111 # Set back to the max 20V SRC mode at the end. 112 self.pdtester.charge(self.pdtester.USBC_MAX_VOLTAGE) 113 114 self.usbpd.send_command('chan 0xffffffff') 115 self.restore_ap_on_power_mode() 116 super(firmware_PDVbusRequest, self).cleanup() 117 118 def run_once(self): 119 """Exectue VBUS request test. 120 121 """ 122 consoles = [self.usbpd, self.pdtester] 123 port_partner = pd_device.PDPortPartner(consoles) 124 125 # Identify a valid test port pair 126 port_pair = port_partner.identify_pd_devices() 127 if not port_pair: 128 raise error.TestFail('No PD connection found!') 129 130 for port in port_pair: 131 if port.is_pdtester: 132 self.pdtester_port = port 133 else: 134 self.dut_port = port 135 136 dut_connect_state = self.dut_port.get_pd_state() 137 logging.info('Initial DUT connect state = %s', dut_connect_state) 138 139 if not self.dut_port.is_connected(dut_connect_state): 140 raise error.TestFail("pd connection not found") 141 142 dut_voltage_limit = self.faft_config.usbc_input_voltage_limit 143 dut_power_voltage_limit = dut_voltage_limit 144 dut_shutdown_and_full_batt_voltage_limit = ( 145 self.faft_config.usbc_voltage_on_shutdown_and_full_batt) 146 147 is_override = self.faft_config.charger_profile_override 148 if is_override: 149 logging.info('*** Custom charger profile takes over, which may ' 150 'cause voltage-not-matched. It is OK to fail. *** ') 151 152 # Test will expect reduced voltage when battery is full and...: 153 # 1. We are running 'shutdown' variant of PDVbusRequest test (indicated 154 # by self.shutdown_power_mode) 155 # 2. EC has battery capability 156 # 3. 'dut_shutdown_and_full_batt_voltage_limit' value will be less than 157 # 'dut_voltage_limit'. By default reduced voltage is set to maximum 158 # voltage which means that no limit applies. Every board needs to 159 # override this to correct value (most likely 5 or 9 volts) 160 is_voltage_reduced_if_batt_full = ( 161 self.shutdown_power_mode 162 and self.check_ec_capability(['battery']) and 163 dut_shutdown_and_full_batt_voltage_limit < dut_voltage_limit) 164 if is_voltage_reduced_if_batt_full: 165 logging.info( 166 '*** This DUT may reduce input voltage to %d volts ' 167 'when battery is full. ***', 168 dut_shutdown_and_full_batt_voltage_limit) 169 170 # Obtain voltage limit due to maximum charging power. Note that this 171 # voltage limit applies only when EC follows the default policy. There 172 # are other policies like PREFER_LOW_VOLTAGE or PREFER_HIGH_VOLTAGE but 173 # they are not implemented in this test. 174 try: 175 srccaps = self.pdtester.get_adapter_source_caps() 176 dut_max_charging_power = self.faft_config.max_charging_power 177 selected_voltage = 0 178 selected_power = 0 179 for (mv, ma) in srccaps: 180 voltage = mv / 1000.0 181 current = ma / 1000.0 182 power = voltage * current 183 184 if (voltage > dut_voltage_limit or power <= selected_power 185 or power > dut_max_charging_power): 186 continue 187 selected_voltage = voltage 188 selected_power = power 189 190 if selected_voltage < dut_power_voltage_limit: 191 dut_power_voltage_limit = selected_voltage 192 logging.info( 193 'EC may request maximum %dV due to adapter\'s max ' 194 'supported power and DUT\'s power constraints. DUT\'s ' 195 'max charging power %dW. Selected charging power %dW', 196 dut_power_voltage_limit, dut_max_charging_power, 197 selected_power) 198 except self.pdtester.PDTesterError: 199 logging.warning('Unable to get charging voltages and currents. ' 200 'Test may fail on high voltages.') 201 202 pdtester_failures = [] 203 logging.info('Start PDTester initiated tests') 204 charging_voltages = self.pdtester.get_charging_voltages() 205 206 if dut_voltage_limit not in charging_voltages: 207 raise error.TestError('Plugged a wrong charger to servo v4? ' 208 '%dV not in supported voltages %s.' % 209 (dut_voltage_limit, str(charging_voltages))) 210 211 for voltage in charging_voltages: 212 logging.info('********* %r *********', voltage) 213 # Set charging voltage 214 self.pdtester.charge(voltage) 215 # Wait for new PD contract to be established 216 time.sleep(self.PD_SETTLE_DELAY) 217 # Get current PDTester PD state 218 pdtester_state = self.pdtester_port.get_pd_state() 219 # If PDTester is in SNK mode and the DUT is in S0, the DUT should 220 # source VBUS = USBC_SINK_VOLTAGE. If PDTester is in SNK mode, and 221 # the DUT is not in S0, the DUT shouldn't source VBUS, which means 222 # VBUS = 0. 223 if self.pdtester_port.is_snk(pdtester_state): 224 expected_vbus_voltage = (self.USBC_SINK_VOLTAGE 225 if self.get_power_state() == 'S0' else 0) 226 ok_to_fail = False 227 elif (is_voltage_reduced_if_batt_full and self._is_batt_full()): 228 expected_vbus_voltage = min( 229 voltage, dut_shutdown_and_full_batt_voltage_limit) 230 ok_to_fail = False 231 else: 232 expected_vbus_voltage = min(voltage, dut_voltage_limit) 233 ok_to_fail = is_override or voltage > dut_power_voltage_limit 234 235 result, result_str = self._compare_vbus(expected_vbus_voltage, 236 ok_to_fail) 237 logging.info('%s, %s', result_str, result) 238 if result == 'FAIL': 239 pdtester_failures.append(result_str) 240 241 # PDTester is set back to 20V SRC mode. 242 self.pdtester.charge(self.pdtester.USBC_MAX_VOLTAGE) 243 time.sleep(self.PD_SETTLE_DELAY) 244 245 if pdtester_failures: 246 logging.error('PDTester voltage source cap failures') 247 for fail in pdtester_failures: 248 logging.error('%s', fail) 249 number = len(pdtester_failures) 250 raise error.TestFail('PDTester failed %d times' % number) 251 252 if (is_voltage_reduced_if_batt_full and self._is_batt_full()): 253 logging.warning('This DUT reduces input voltage when chipset is in ' 254 'G3/S5 and battery is full. DUT initiated tests ' 255 'will be skipped. Please discharge battery to level ' 256 'that allows charging and run this test again') 257 return 258 259 # The DUT must be in SNK mode for the pd <port> dev <voltage> 260 # command to have an effect. 261 if not self.dut_port.is_snk(): 262 # DUT needs to be in SINK Mode, attempt to force change 263 self.dut_port.drp_set('snk') 264 time.sleep(self.PD_SETTLE_DELAY) 265 if not self.dut_port.is_snk(): 266 raise error.TestFail("DUT not able to connect in SINK mode") 267 268 logging.info('Start of DUT initiated tests') 269 dut_failures = [] 270 for v in self.VOLTAGE_SEQUENCE: 271 if v > dut_voltage_limit: 272 logging.info('Target = %02dV: skipped, over the limit %0dV', 273 v, dut_voltage_limit) 274 continue 275 if v not in charging_voltages: 276 logging.info( 277 'Target = %02dV: skipped, voltage unsupported, ' 278 'update hdctools and servo_v4 firmware ' 279 'or attach a different charger', v) 280 continue 281 # Build 'pd <port> dev <voltage> command 282 cmd = 'pd %d dev %d' % (self.dut_port.port, v) 283 self.dut_port.utils.send_pd_command(cmd) 284 time.sleep(self.PD_SETTLE_DELAY) 285 ok_to_fail = is_override or v > dut_power_voltage_limit 286 result, result_str = self._compare_vbus(v, ok_to_fail) 287 logging.info('%s, %s', result_str, result) 288 if result == 'FAIL': 289 dut_failures.append(result_str) 290 291 # Make sure DUT is set back to its max voltage so DUT will accept all 292 # options 293 cmd = 'pd %d dev %d' % (self.dut_port.port, dut_voltage_limit) 294 self.dut_port.utils.send_pd_command(cmd) 295 time.sleep(self.PD_SETTLE_DELAY) 296 # The next group of tests need DUT to connect in SNK and SRC modes 297 self.dut_port.drp_set('on') 298 299 if dut_failures: 300 logging.error('DUT voltage request failures') 301 for fail in dut_failures: 302 logging.error('%s', fail) 303 number = len(dut_failures) 304 raise error.TestFail('DUT failed %d times' % number) 305