• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_console
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
37    PD_SETTLE_DELAY = 4
38    USBC_SINK_VOLTAGE = 5
39    VBUS_TOLERANCE = 0.12
40
41    VOLTAGE_SEQUENCE = [5, 12, 20, 12, 5, 20, 5, 5, 12, 12, 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
54        tolerance = self.VBUS_TOLERANCE * expected_vbus_voltage
55        voltage_difference = math.fabs(expected_vbus_voltage - vbus_voltage)
56        result_str = 'Target = %02dV:\tAct = %.2f\tDelta = %.2f' % \
57                     (expected_vbus_voltage, vbus_voltage, voltage_difference)
58        # Verify that measured Vbus voltage is within expected range
59        voltage_difference = math.fabs(expected_vbus_voltage - vbus_voltage)
60        if voltage_difference > tolerance:
61            result = 'ALLOWED_FAIL' if ok_to_fail else 'FAIL'
62        else:
63            result = 'PASS'
64        return result, result_str
65
66    def initialize(self, host, cmdline_args, flip_cc=False):
67        super(firmware_PDVbusRequest, self).initialize(host, cmdline_args)
68        self.setup_pdtester(flip_cc)
69        # Only run in normal mode
70        self.switcher.setup_mode('normal')
71        self.usbpd.send_command('chan 0')
72
73    def cleanup(self):
74        # Set back to the max 20V SRC mode at the end.
75        self.pdtester.charge(self.pdtester.USBC_MAX_VOLTAGE)
76
77        self.usbpd.send_command('chan 0xffffffff')
78        super(firmware_PDVbusRequest, self).cleanup()
79
80    def run_once(self):
81        """Exectue VBUS request test.
82
83        """
84        # TODO(b/35573842): Refactor to use PDPortPartner to probe the port
85        self.pdtester_port = 1 if 'servo_v4' in self.pdtester.servo_type else 0
86
87        # create objects for pd utilities
88        pd_dut_utils = pd_console.PDConsoleUtils(self.usbpd)
89        pd_pdtester_utils = pd_console.PDConsoleUtils(self.pdtester)
90
91        # Make sure PD support exists in the UART console
92        if pd_dut_utils.verify_pd_console() == False:
93            raise error.TestFail("pd command not present on console!")
94
95        # Type C connection (PD contract) should exist at this point
96        dut_state = pd_dut_utils.query_pd_connection()
97        logging.info('DUT PD connection state: %r', dut_state)
98        if dut_state['connect'] == False:
99            raise error.TestFail("pd connection not found")
100
101        dut_voltage_limit = self.faft_config.usbc_input_voltage_limit
102        is_override = self.faft_config.charger_profile_override
103        if is_override:
104            logging.info('*** Custom charger profile takes over, which may '
105                         'cause voltage-not-matched. It is OK to fail. *** ')
106
107        pdtester_failures = []
108        logging.info('Start PDTester initiated tests')
109        for voltage in self.pdtester.get_charging_voltages():
110            logging.info('********* %r *********', voltage)
111            # Set charging voltage
112            self.pdtester.charge(voltage)
113            # Wait for new PD contract to be established
114            time.sleep(self.PD_SETTLE_DELAY)
115            # Get current PDTester PD state
116            pdtester_state = pd_pdtester_utils.get_pd_state(self.pdtester_port)
117            # If PDTester is sink, then Vbus_exp = 5v, not skip failure even
118            # using charger profile override.
119            if pdtester_state == pd_pdtester_utils.SNK_CONNECT:
120                expected_vbus_voltage = self.USBC_SINK_VOLTAGE
121                ok_to_fail = False
122            else:
123                expected_vbus_voltage = min(voltage, dut_voltage_limit)
124                ok_to_fail = is_override
125
126            result, result_str = self._compare_vbus(expected_vbus_voltage,
127                                                    ok_to_fail)
128            logging.info('%s, %s', result_str, result)
129            if result == 'FAIL':
130                pdtester_failures.append(result_str)
131
132        # PDTester is set back to 20V SRC mode.
133        self.pdtester.charge(self.pdtester.USBC_MAX_VOLTAGE)
134        time.sleep(self.PD_SETTLE_DELAY)
135
136        if pdtester_failures:
137            logging.error('PDTester voltage source cap failures')
138            for fail in pdtester_failures:
139                logging.error('%s', fail)
140            number = len(pdtester_failures)
141            raise error.TestFail('PDTester failed %d times' % number)
142
143        # The DUT must be in SNK mode for the pd <port> dev <voltage>
144        # command to have an effect.
145        if dut_state['role'] != pd_dut_utils.SNK_CONNECT:
146            # DUT needs to be in SINK Mode, attempt to force change
147            pd_dut_utils.set_pd_dualrole(dut_state['port'], 'snk')
148            time.sleep(self.PD_SETTLE_DELAY)
149            if (pd_dut_utils.get_pd_state(dut_state['port']) !=
150                pd_dut_utils.SNK_CONNECT):
151                raise error.TestFail("DUT not able to connect in SINK mode")
152
153        logging.info('Start of DUT initiated tests')
154        dut_failures = []
155        for v in self.VOLTAGE_SEQUENCE:
156            if v > dut_voltage_limit:
157                logging.info('Target = %02dV: skipped, over the limit %0dV',
158                             v, dut_voltage_limit)
159                continue
160            # Build 'pd <port> dev <voltage> command
161            cmd = 'pd %d dev %d' % (dut_state['port'], v)
162            pd_dut_utils.send_pd_command(cmd)
163            time.sleep(self.PD_SETTLE_DELAY)
164            result, result_str = self._compare_vbus(v, ok_to_fail=is_override)
165            logging.info('%s, %s', result_str, result)
166            if result == 'FAIL':
167                dut_failures.append(result_str)
168
169        # Make sure DUT is set back to its max voltage so DUT will accept all
170        # options
171        cmd = 'pd %d dev %d' % (dut_state['port'], dut_voltage_limit)
172        pd_dut_utils.send_pd_command(cmd)
173        time.sleep(self.PD_SETTLE_DELAY)
174        # The next group of tests need DUT to connect in SNK and SRC modes
175        pd_dut_utils.set_pd_dualrole(dut_state['port'], 'on')
176
177        if dut_failures:
178            logging.error('DUT voltage request failures')
179            for fail in dut_failures:
180                logging.error('%s', fail)
181            number = len(dut_failures)
182            raise error.TestFail('DUT failed %d times' % number)
183