| # Copyright 2016 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. |
| |
| import logging |
| import math |
| import time |
| |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.server.cros.faft.firmware_test import FirmwareTest |
| from autotest_lib.server.cros.servo import pd_device |
| |
| |
| class firmware_PDVbusRequest(FirmwareTest): |
| """ |
| Servo based USB PD VBUS level test. This test is written to use both |
| the DUT and PDTester test board. It requires that the DUT support |
| dualrole (SRC or SNK) operation. VBUS change requests occur in two |
| methods. |
| |
| The 1st test initiates the VBUS change by using special PDTester |
| feature to send new SRC CAP message. This causes the DUT to request |
| a new VBUS voltage matching what's in the SRC CAP message. |
| |
| The 2nd test configures the DUT in SNK mode and uses the pd console |
| command 'pd 0/1 dev V' command where V is the desired voltage |
| 5/12/20. This test is more risky and won't be executed if the 1st |
| test is failed. If the DUT max input voltage is not 20V, like 12V, |
| and the FAFT config is set wrong, it may negotiate to a voltage |
| higher than it can support, that may damage the DUT. |
| |
| Pass critera is all voltage transitions are successful. |
| |
| """ |
| version = 1 |
| PD_SETTLE_DELAY = 10 |
| USBC_SINK_VOLTAGE = 5 |
| VBUS_TOLERANCE = 0.12 |
| |
| VOLTAGE_SEQUENCE = [5, 9, 10, 12, 15, 20, 15, 12, 9, 5, 20, |
| 5, 5, 9, 9, 10, 10, 12, 12, 15, 15, 20] |
| |
| def _compare_vbus(self, expected_vbus_voltage, ok_to_fail): |
| """Check VBUS using pdtester |
| |
| @param expected_vbus_voltage: nominal VBUS level (in volts) |
| @param ok_to_fail: True to not treat voltage-not-matched as failure. |
| |
| @returns: a tuple containing pass/fail indication and logging string |
| """ |
| # Get Vbus voltage and current |
| vbus_voltage = self.pdtester.vbus_voltage |
| # Compute voltage tolerance range. To handle the case where VBUS is |
| # off, set the minimal tolerance to USBC_SINK_VOLTAGE * VBUS_TOLERANCE. |
| tolerance = (self.VBUS_TOLERANCE * max(expected_vbus_voltage, |
| self.USBC_SINK_VOLTAGE)) |
| voltage_difference = math.fabs(expected_vbus_voltage - vbus_voltage) |
| result_str = 'Target = %02dV:\tAct = %.2f\tDelta = %.2f' % \ |
| (expected_vbus_voltage, vbus_voltage, voltage_difference) |
| # Verify that measured Vbus voltage is within expected range |
| if voltage_difference > tolerance: |
| result = 'ALLOWED_FAIL' if ok_to_fail else 'FAIL' |
| else: |
| result = 'PASS' |
| return result, result_str |
| |
| def initialize(self, host, cmdline_args, flip_cc=False, dts_mode=False, |
| init_power_mode=None): |
| super(firmware_PDVbusRequest, self).initialize(host, cmdline_args) |
| # Only run on DUTs that can supply battery power. |
| if not self._client.has_battery(): |
| raise error.TestNAError("DUT type does not have a battery.") |
| self.setup_pdtester(flip_cc, dts_mode) |
| # Only run in normal mode |
| self.switcher.setup_mode('normal') |
| if init_power_mode: |
| # Set the DUT to suspend or shutdown mode |
| self.set_ap_off_power_mode(init_power_mode) |
| self.usbpd.send_command('chan 0') |
| |
| def cleanup(self): |
| # Set back to the max 20V SRC mode at the end. |
| self.pdtester.charge(self.pdtester.USBC_MAX_VOLTAGE) |
| |
| self.usbpd.send_command('chan 0xffffffff') |
| self.restore_ap_on_power_mode() |
| super(firmware_PDVbusRequest, self).cleanup() |
| |
| def run_once(self): |
| """Exectue VBUS request test. |
| |
| """ |
| consoles = [self.usbpd, self.pdtester] |
| port_partner = pd_device.PDPortPartner(consoles) |
| |
| # Identify a valid test port pair |
| port_pair = port_partner.identify_pd_devices() |
| if not port_pair: |
| raise error.TestFail('No PD connection found!') |
| |
| for port in port_pair: |
| if port.is_pdtester: |
| self.pdtester_port = port |
| else: |
| self.dut_port = port |
| |
| dut_connect_state = self.dut_port.get_pd_state() |
| logging.info('Initial DUT connect state = %s', dut_connect_state) |
| |
| if not self.dut_port.is_connected(dut_connect_state): |
| raise error.TestFail("pd connection not found") |
| |
| dut_voltage_limit = self.faft_config.usbc_input_voltage_limit |
| is_override = self.faft_config.charger_profile_override |
| if is_override: |
| logging.info('*** Custom charger profile takes over, which may ' |
| 'cause voltage-not-matched. It is OK to fail. *** ') |
| |
| pdtester_failures = [] |
| logging.info('Start PDTester initiated tests') |
| charging_voltages = self.pdtester.get_charging_voltages() |
| |
| if dut_voltage_limit not in charging_voltages: |
| raise error.TestError('Plugged a wrong charger to servo v4? ' |
| '%dV not in supported voltages %s.' % |
| (dut_voltage_limit, str(charging_voltages))) |
| |
| for voltage in charging_voltages: |
| logging.info('********* %r *********', voltage) |
| # Set charging voltage |
| self.pdtester.charge(voltage) |
| # Wait for new PD contract to be established |
| time.sleep(self.PD_SETTLE_DELAY) |
| # Get current PDTester PD state |
| pdtester_state = self.pdtester_port.get_pd_state() |
| # If PDTester is in SNK mode and the DUT is in S0, the DUT should |
| # source VBUS = USBC_SINK_VOLTAGE. If PDTester is in SNK mode, and |
| # the DUT is not in S0, the DUT shouldn't source VBUS, which means |
| # VBUS = 0. |
| if self.pdtester_port.is_snk(pdtester_state): |
| expected_vbus_voltage = (self.USBC_SINK_VOLTAGE |
| if self.get_power_state() == 'S0' else 0) |
| ok_to_fail = False |
| else: |
| expected_vbus_voltage = min(voltage, dut_voltage_limit) |
| ok_to_fail = is_override |
| |
| result, result_str = self._compare_vbus(expected_vbus_voltage, |
| ok_to_fail) |
| logging.info('%s, %s', result_str, result) |
| if result == 'FAIL': |
| pdtester_failures.append(result_str) |
| |
| # PDTester is set back to 20V SRC mode. |
| self.pdtester.charge(self.pdtester.USBC_MAX_VOLTAGE) |
| time.sleep(self.PD_SETTLE_DELAY) |
| |
| if pdtester_failures: |
| logging.error('PDTester voltage source cap failures') |
| for fail in pdtester_failures: |
| logging.error('%s', fail) |
| number = len(pdtester_failures) |
| raise error.TestFail('PDTester failed %d times' % number) |
| |
| # The DUT must be in SNK mode for the pd <port> dev <voltage> |
| # command to have an effect. |
| if not self.dut_port.is_snk(): |
| # DUT needs to be in SINK Mode, attempt to force change |
| self.dut_port.drp_set('snk') |
| time.sleep(self.PD_SETTLE_DELAY) |
| if not self.dut_port.is_snk(): |
| raise error.TestFail("DUT not able to connect in SINK mode") |
| |
| logging.info('Start of DUT initiated tests') |
| dut_failures = [] |
| for v in self.VOLTAGE_SEQUENCE: |
| if v > dut_voltage_limit: |
| logging.info('Target = %02dV: skipped, over the limit %0dV', |
| v, dut_voltage_limit) |
| continue |
| if v not in charging_voltages: |
| logging.info('Target = %02dV: skipped, voltage unsupported, ' |
| 'update hdctools and servo_v4 firmware', v) |
| continue |
| # Build 'pd <port> dev <voltage> command |
| cmd = 'pd %d dev %d' % (self.dut_port.port, v) |
| self.dut_port.utils.send_pd_command(cmd) |
| time.sleep(self.PD_SETTLE_DELAY) |
| result, result_str = self._compare_vbus(v, ok_to_fail=is_override) |
| logging.info('%s, %s', result_str, result) |
| if result == 'FAIL': |
| dut_failures.append(result_str) |
| |
| # Make sure DUT is set back to its max voltage so DUT will accept all |
| # options |
| cmd = 'pd %d dev %d' % (self.dut_port.port, dut_voltage_limit) |
| self.dut_port.utils.send_pd_command(cmd) |
| time.sleep(self.PD_SETTLE_DELAY) |
| # The next group of tests need DUT to connect in SNK and SRC modes |
| self.dut_port.drp_set('on') |
| |
| if dut_failures: |
| logging.error('DUT voltage request failures') |
| for fail in dut_failures: |
| logging.error('%s', fail) |
| number = len(dut_failures) |
| raise error.TestFail('DUT failed %d times' % number) |