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 Plankton test board. It requires that the DUT support
18    dualrole (SRC or SNK) operation. VBUS change requests occur in two
19    methods. First, with the DUT in SNK mode, it uses the pd console command
20    'pd 0/1 dev V' command where V is the desired voltage 5/12/20. The 2nd
21    test initiates the VBUS change by using special Plankton feature to
22    send new SRC CAP message. This causes the DUT to request a new VBUS
23    voltage mathcing what's in the SRC CAP message.
24
25    Pass critera is all voltage transitions are successful.
26
27    """
28    version = 1
29
30    PD_SETTLE_DELAY = 4
31    USBC_SINK_VOLTAGE = 5
32    USBC_MAX_VOLTAGE = 20
33    VBUS_TOLERANCE = 0.12
34
35    VOLTAGE_SEQUENCE = [5, 12, 20, 12, 5, 20, 5, 5, 12, 12, 20]
36
37    def _compare_vbus(self, expected_vbus_voltage):
38        """Check VBUS using plankton
39
40        @param expected_vbus_voltage: nominal VBUS level (in volts)
41
42        @returns: a tuple containing pass/fail indication and logging string
43        """
44        # Get Vbus voltage and current
45        vbus_voltage = self.plankton.vbus_voltage
46        vbus_current = self.plankton.vbus_current
47        # Compute voltage tolerance range
48        tolerance = self.VBUS_TOLERANCE * expected_vbus_voltage
49        voltage_difference = math.fabs(expected_vbus_voltage - vbus_voltage)
50        result_str = 'Target = %02dV:\tAct = %.2f\tDelta = %.2f' % \
51                     (expected_vbus_voltage, vbus_voltage, voltage_difference)
52        # Verify that measured Vbus voltage is within expected range
53        voltage_difference = math.fabs(expected_vbus_voltage - vbus_voltage)
54        if voltage_difference > tolerance:
55            result = 'FAIL'
56        else:
57            result = 'PASS'
58        return result, result_str
59
60    def initialize(self, host, cmdline_args):
61        super(firmware_PDVbusRequest, self).initialize(host, cmdline_args)
62        # Only run in normal mode
63        self.switcher.setup_mode('normal')
64        self.usbpd.send_command('chan 0')
65
66    def cleanup(self):
67        self.usbpd.send_command('chan 0xffffffff')
68        super(firmware_PDVbusRequest, self).cleanup()
69
70    def run_once(self):
71        """Exectue VBUS request test.
72
73        """
74
75        # create objects for pd utilities
76        pd_dut_utils = pd_console.PDConsoleUtils(self.usbpd)
77        pd_plankton_utils = pd_console.PDConsoleUtils(self.plankton)
78
79        # Make sure PD support exists in the UART console
80        if pd_dut_utils.verify_pd_console() == False:
81            raise error.TestFail("pd command not present on console!")
82
83        # Type C connection (PD contract) should exist at this point
84        dut_state = pd_dut_utils.query_pd_connection()
85        logging.info('DUT PD connection state: %r', dut_state)
86        if dut_state['connect'] == False:
87            raise error.TestFail("pd connection not found")
88        if dut_state['role'] != pd_dut_utils.SNK_CONNECT:
89            # DUT needs to be in SINK Mode, attempt to force change
90            pd_dut_utils.set_pd_dualrole('snk')
91            time.sleep(self.PD_SETTLE_DELAY)
92            if pd_dut_utils.get_pd_state(dut_state['port']) != pd_dut_utils.SNK_CONNECT:
93                raise error.TestFail("DUT not able to connect in SINK mode")
94
95        # Plankton must be set to 20V SRC mode in order for the DUT
96        # to be able to request all 3 possible voltage levels (5, 12, 20).
97        # The DUT must be in SNK mode for the pd <port> dev <voltage>
98        # command to have an effect.
99        self.plankton.charge(self.USBC_MAX_VOLTAGE)
100        time.sleep(self.PD_SETTLE_DELAY)
101        logging.info('Start of DUT initiated tests')
102        dut_failures = []
103        for v in self.VOLTAGE_SEQUENCE:
104            # Build 'pd <port> dev <voltage> command
105            cmd = 'pd %d dev %d' % (dut_state['port'], v)
106            pd_dut_utils.send_pd_command(cmd)
107            time.sleep(self.PD_SETTLE_DELAY)
108            result, result_str = self._compare_vbus(v)
109            logging.info('%s, %s', result_str, result)
110            if result == 'FAIL':
111                dut_failures.append(result_str)
112
113        # Make sure Plankton is set back to 20VSRC so DUT will accept all options
114        cmd = 'pd %d dev %d' % (dut_state['port'], self.USBC_MAX_VOLTAGE)
115        time.sleep(self.PD_SETTLE_DELAY)
116        # The next group of tests need DUT to connect in SNK and SRC modes
117        pd_dut_utils.set_pd_dualrole('on')
118
119        plankton_failures = []
120        logging.info('Start Plankton initiated tests')
121        for voltage in self.plankton.get_charging_voltages():
122            logging.info('********* %r *********', voltage)
123            # Set charging voltage
124            self.plankton.charge(voltage)
125            # Wait for new PD contract to be established
126            time.sleep(self.PD_SETTLE_DELAY)
127            # Get current Plankton PD state
128            plankton_state = pd_plankton_utils.get_pd_state(0)
129            expected_vbus_voltage = self.plankton.charging_voltage
130            # If Plankton is sink, then Vbus_exp = 5v
131            if plankton_state == pd_plankton_utils.SNK_CONNECT:
132                expected_vbus_voltage = self.USBC_SINK_VOLTAGE
133            result, result_str = self._compare_vbus(expected_vbus_voltage)
134            logging.info('%s, %s', result_str, result)
135            if result == 'FAIL':
136                plankton_failures.append(result_str)
137
138        if dut_failures:
139            logging.error('DUT voltage request failures')
140            for fail in dut_failures:
141                logging.error('%s', fail)
142
143        if plankton_failures:
144            logging.error('Plankton voltage source cap failures')
145            for fail in plankton_failures:
146                logging.error('%s', fail)
147
148        if dut_failures or plankton_failures:
149            if dut_failures and plankton_failures:
150                test = 'DUT and Plankton'
151                number = len(dut_failures) + len(plankton_failures)
152            elif dut_failures:
153                test = 'DUT'
154                number = len(dut_failures)
155            else:
156                test = 'Plankton'
157                number = len(plankton_failures)
158            raise error.TestFail('%s failed %d times' % (test, number))
159