• 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_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