xref: /aosp_15_r20/external/autotest/server/cros/servo/pdtester.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright 2015 The Chromium OS Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li
6*9c5db199SXin Liimport logging
7*9c5db199SXin Liimport re
8*9c5db199SXin Liimport time
9*9c5db199SXin Li
10*9c5db199SXin Lifrom autotest_lib.client.bin import utils
11*9c5db199SXin Lifrom autotest_lib.server.cros.servo import chrome_ec
12*9c5db199SXin Li
13*9c5db199SXin Li
14*9c5db199SXin Liclass PDTesterError(Exception):
15*9c5db199SXin Li    """Error object for PDTester"""
16*9c5db199SXin Li    pass
17*9c5db199SXin Li
18*9c5db199SXin Li
19*9c5db199SXin Liclass PDTester(chrome_ec.ChromeEC):
20*9c5db199SXin Li    """Manages control of a PDTester hardware.
21*9c5db199SXin Li
22*9c5db199SXin Li    PDTester is a general term for hardware developed to aid in USB type-C
23*9c5db199SXin Li    debug and control of various type C host devices. It can be either a
24*9c5db199SXin Li    Plankton board or a Servo v4 board.
25*9c5db199SXin Li
26*9c5db199SXin Li    We control the PDTester board via the UART and the Servod interfaces.
27*9c5db199SXin Li    PDTester provides many interfaces that access the hardware. It can
28*9c5db199SXin Li    also be passed into the PDConsoleUtils as a console which then
29*9c5db199SXin Li    provides methods to access the pd console.
30*9c5db199SXin Li
31*9c5db199SXin Li    This class is to abstract these interfaces.
32*9c5db199SXin Li    """
33*9c5db199SXin Li    # USB charging command delays in seconds.
34*9c5db199SXin Li    USBC_COMMAND_DELAY = 0.5
35*9c5db199SXin Li    # PDTester USBC commands.
36*9c5db199SXin Li    USBC_DRSWAP = 'usbc_drswap'
37*9c5db199SXin Li    USBC_PRSWAP = 'usbc_prswap'
38*9c5db199SXin Li    USBC_ROLE = 'usbc_role'  # TODO(b:140256624): deprecate by USBC_PR
39*9c5db199SXin Li    USBC_PR = 'usbc_pr'
40*9c5db199SXin Li    USBC_MUX = 'usbc_mux'
41*9c5db199SXin Li    RE_USBC_ROLE_VOLTAGE = r'src(\d+)v'
42*9c5db199SXin Li    USBC_SRC_CAPS = 'ada_srccaps'
43*9c5db199SXin Li    USBC_CHARGING_VOLTAGES = {
44*9c5db199SXin Li        0: 'sink',
45*9c5db199SXin Li        5: 'src5v',
46*9c5db199SXin Li        9: 'src9v',
47*9c5db199SXin Li        10: 'src10v',
48*9c5db199SXin Li        12: 'src12v',
49*9c5db199SXin Li        15: 'src15v',
50*9c5db199SXin Li        20: 'src20v'}
51*9c5db199SXin Li    # TODO(b:140256624): deprecate by USBC_CHARGING_VOLTAGES
52*9c5db199SXin Li    USBC_CHARGING_VOLTAGES_LEGACY = {
53*9c5db199SXin Li        0: 'sink',
54*9c5db199SXin Li        5: 'src5v',
55*9c5db199SXin Li        12: 'src12v',
56*9c5db199SXin Li        20: 'src20v'}
57*9c5db199SXin Li    USBC_MAX_VOLTAGE = 20
58*9c5db199SXin Li    VBUS_VOLTAGE_MV = 'vbus_voltage'
59*9c5db199SXin Li    VBUS_CURRENT_MA = 'vbus_current'
60*9c5db199SXin Li    VBUS_POWER_MW = 'vbus_power'
61*9c5db199SXin Li    # USBC PD states.
62*9c5db199SXin Li    USBC_PD_STATES = {
63*9c5db199SXin Li        'sink': 'SNK_READY',
64*9c5db199SXin Li        'source': 'SRC_READY'}
65*9c5db199SXin Li    POLL_STATE_SECS = 2
66*9c5db199SXin Li    FIRST_PD_SETUP_ELEMENT = ['servo_v4', 'servo_v4p1']
67*9c5db199SXin Li    SECOND_PD_SETUP_ELEMENT = ['servo_micro', 'c2d2']
68*9c5db199SXin Li
69*9c5db199SXin Li    def __init__(self, servo, servod_proxy):
70*9c5db199SXin Li        """Initialize and keep the servo object.
71*9c5db199SXin Li
72*9c5db199SXin Li        @param servo: A Servo object
73*9c5db199SXin Li        @param servod_proxy: Servod proxy for pdtester host
74*9c5db199SXin Li        """
75*9c5db199SXin Li        self.servo_type = servo.get_servo_version()
76*9c5db199SXin Li        pd_tester_device = self.servo_type.split('_with_')[0]
77*9c5db199SXin Li        if pd_tester_device in self.FIRST_PD_SETUP_ELEMENT:
78*9c5db199SXin Li            uart_prefix = pd_tester_device + "_uart"
79*9c5db199SXin Li        else:
80*9c5db199SXin Li            uart_prefix = 'ec_uart'
81*9c5db199SXin Li
82*9c5db199SXin Li        super(PDTester, self).__init__(servo, uart_prefix)
83*9c5db199SXin Li        # save servod proxy for methods that access PDTester servod
84*9c5db199SXin Li        self._server = servod_proxy
85*9c5db199SXin Li        self.init_hardware()
86*9c5db199SXin Li
87*9c5db199SXin Li
88*9c5db199SXin Li    def init_hardware(self):
89*9c5db199SXin Li        """Initializes PDTester hardware."""
90*9c5db199SXin Li        if self.servo_type == 'plankton':
91*9c5db199SXin Li            if not int(self.get('debug_usb_sel')):
92*9c5db199SXin Li                raise PDTesterError('debug_usb_sel (SW3) should be ON!! '
93*9c5db199SXin Li                                    'Please use CN15 to connect Plankton.')
94*9c5db199SXin Li            self.set('typec_to_hub_sw', '0')
95*9c5db199SXin Li            self.set('usb2_mux_sw', '1')
96*9c5db199SXin Li            self.set('usb_dn_pwren', 'on')
97*9c5db199SXin Li
98*9c5db199SXin Li
99*9c5db199SXin Li    def set(self, control_name, value):
100*9c5db199SXin Li        """Sets the value of a control using servod.
101*9c5db199SXin Li
102*9c5db199SXin Li        @param control_name: pdtester servo control item
103*9c5db199SXin Li        @param value: value to set pdtester servo control item
104*9c5db199SXin Li        """
105*9c5db199SXin Li        assert control_name
106*9c5db199SXin Li        self._server.set(control_name, value)
107*9c5db199SXin Li
108*9c5db199SXin Li
109*9c5db199SXin Li    def get(self, control_name):
110*9c5db199SXin Li        """Gets the value of a control from servod.
111*9c5db199SXin Li
112*9c5db199SXin Li        @param control_name: pdtester servo control item
113*9c5db199SXin Li        """
114*9c5db199SXin Li        assert control_name
115*9c5db199SXin Li        return self._server.get(control_name)
116*9c5db199SXin Li
117*9c5db199SXin Li
118*9c5db199SXin Li    @property
119*9c5db199SXin Li    def vbus_voltage(self):
120*9c5db199SXin Li        """Gets PDTester VBUS voltage in volts."""
121*9c5db199SXin Li        return float(self.get(self.VBUS_VOLTAGE_MV)) / 1000.0
122*9c5db199SXin Li
123*9c5db199SXin Li
124*9c5db199SXin Li    @property
125*9c5db199SXin Li    def vbus_current(self):
126*9c5db199SXin Li        """Gets PDTester VBUS current in amps."""
127*9c5db199SXin Li        return float(self.get(self.VBUS_CURRENT_MA)) / 1000.0
128*9c5db199SXin Li
129*9c5db199SXin Li
130*9c5db199SXin Li    @property
131*9c5db199SXin Li    def vbus_power(self):
132*9c5db199SXin Li        """Gets PDTester charging power in watts."""
133*9c5db199SXin Li        return float(self.get(self.VBUS_POWER_MW)) / 1000.0
134*9c5db199SXin Li
135*9c5db199SXin Li    def get_adapter_source_caps(self):
136*9c5db199SXin Li        """Gets a list of SourceCap Tuples in mV/mA."""
137*9c5db199SXin Li        try:
138*9c5db199SXin Li            res = self.get(self.USBC_SRC_CAPS)
139*9c5db199SXin Li        except:
140*9c5db199SXin Li            raise PDTesterError('Unsupported servov4 command(%s). '
141*9c5db199SXin Li                                'Maybe firmware or servod too old? '
142*9c5db199SXin Li                                'sudo servo_updater -b servo_v4; '
143*9c5db199SXin Li                                'sudo emerge hdctools' % self.USBC_SRC_CAPS)
144*9c5db199SXin Li
145*9c5db199SXin Li        srccaps = []
146*9c5db199SXin Li        for pdo_str in res:
147*9c5db199SXin Li            m = re.match(r'\d: (\d+)mV/(\d+)mA', pdo_str)
148*9c5db199SXin Li            srccaps.append((int(m.group(1)), int(m.group(2))))
149*9c5db199SXin Li        return srccaps
150*9c5db199SXin Li
151*9c5db199SXin Li    def get_charging_voltages(self):
152*9c5db199SXin Li        """Gets the lists of available charging voltages of the adapter."""
153*9c5db199SXin Li        try:
154*9c5db199SXin Li            srccaps = self.get_adapter_source_caps()
155*9c5db199SXin Li        except PDTesterError:
156*9c5db199SXin Li            # htctools and servov4 is not updated, fallback to the old path.
157*9c5db199SXin Li            logging.warning('hdctools or servov4 firmware too old, fallback to '
158*9c5db199SXin Li                         'fixed charging voltages.')
159*9c5db199SXin Li            return list(self.USBC_CHARGING_VOLTAGES_LEGACY.keys())
160*9c5db199SXin Li
161*9c5db199SXin Li        # insert 0 voltage for sink
162*9c5db199SXin Li        vols = [0]
163*9c5db199SXin Li        for pdo in srccaps:
164*9c5db199SXin Li            # Only include the voltages that are in USBC_CHARGING_VOLTAGES
165*9c5db199SXin Li            if pdo[0] / 1000 in self.USBC_CHARGING_VOLTAGES:
166*9c5db199SXin Li                vols.append(pdo[0] / 1000)
167*9c5db199SXin Li            else:
168*9c5db199SXin Li                logging.debug("Omitting unsupported PDO = %s", pdo)
169*9c5db199SXin Li        return vols
170*9c5db199SXin Li
171*9c5db199SXin Li    def charge(self, voltage):
172*9c5db199SXin Li        """Sets PDTester to provide power at specific voltage.
173*9c5db199SXin Li
174*9c5db199SXin Li        @param voltage: Specified charging voltage in volts.
175*9c5db199SXin Li        """
176*9c5db199SXin Li        charging_voltages = self.get_charging_voltages()
177*9c5db199SXin Li        if voltage not in charging_voltages:
178*9c5db199SXin Li            logging.warning(
179*9c5db199SXin Li                    'Unsupported voltage(%s) of the adapter. '
180*9c5db199SXin Li                    'Maybe firmware or servod too old? '
181*9c5db199SXin Li                    'sudo servo_updater -b servo_v4; '
182*9c5db199SXin Li                    'sudo emerge hdctools', voltage)
183*9c5db199SXin Li        if voltage not in self.USBC_CHARGING_VOLTAGES:
184*9c5db199SXin Li            raise PDTesterError(
185*9c5db199SXin Li                    'Cannot set voltage to %s, not supported by %s' %
186*9c5db199SXin Li                    (voltage, self.USBC_PR))
187*9c5db199SXin Li
188*9c5db199SXin Li        try:
189*9c5db199SXin Li            self.set(self.USBC_PR, self.USBC_CHARGING_VOLTAGES[voltage])
190*9c5db199SXin Li        except:
191*9c5db199SXin Li            if voltage not in self.USBC_CHARGING_VOLTAGES_LEGACY:
192*9c5db199SXin Li                raise PDTesterError(
193*9c5db199SXin Li                        'Cannot set voltage to %s, not supported by %s' %
194*9c5db199SXin Li                        (voltage, self.USBC_ROLE))
195*9c5db199SXin Li            self.set(self.USBC_ROLE,
196*9c5db199SXin Li                     self.USBC_CHARGING_VOLTAGES_LEGACY[voltage])
197*9c5db199SXin Li        time.sleep(self.USBC_COMMAND_DELAY)
198*9c5db199SXin Li
199*9c5db199SXin Li    @property
200*9c5db199SXin Li    def charging_voltage(self):
201*9c5db199SXin Li        """Gets current charging voltage."""
202*9c5db199SXin Li        try:
203*9c5db199SXin Li            usbc_pr = self.get(self.USBC_PR)
204*9c5db199SXin Li        except:
205*9c5db199SXin Li            logging.warning(
206*9c5db199SXin Li                    'Unsupported control(%s). '
207*9c5db199SXin Li                    'Maybe firmware or servod too old? '
208*9c5db199SXin Li                    'sudo servo_updater -b servo_v4; '
209*9c5db199SXin Li                    'sudo emerge hdctools', self.USBC_PR)
210*9c5db199SXin Li            usbc_pr = self.get(self.USBC_ROLE)
211*9c5db199SXin Li        m = re.match(self.RE_USBC_ROLE_VOLTAGE, usbc_pr)
212*9c5db199SXin Li        if m:
213*9c5db199SXin Li            return int(m.group(1))
214*9c5db199SXin Li
215*9c5db199SXin Li        if usbc_pr == self.USBC_CHARGING_VOLTAGES[0]:
216*9c5db199SXin Li            return 0
217*9c5db199SXin Li
218*9c5db199SXin Li        raise PDTesterError('Invalid USBC power role: %s' % usbc_pr)
219*9c5db199SXin Li
220*9c5db199SXin Li
221*9c5db199SXin Li    def poll_pd_state(self, state):
222*9c5db199SXin Li        """Polls until PDTester pd goes to the specific state.
223*9c5db199SXin Li
224*9c5db199SXin Li        @param state: Specified pd state name.
225*9c5db199SXin Li        """
226*9c5db199SXin Li        if state not in self.USBC_PD_STATES:
227*9c5db199SXin Li            raise PDTesterError('Invalid state name: %s' % state)
228*9c5db199SXin Li        utils.poll_for_condition(
229*9c5db199SXin Li            lambda: self.get('pd_state') == self.USBC_PD_STATES[state],
230*9c5db199SXin Li            exception=utils.TimeoutError('PDTester not in %s state '
231*9c5db199SXin Li                                         'after %s seconds.' %
232*9c5db199SXin Li                                         (self.USBC_PD_STATES[state],
233*9c5db199SXin Li                                          self.POLL_STATE_SECS)),
234*9c5db199SXin Li            timeout=self.POLL_STATE_SECS)
235*9c5db199SXin Li
236*9c5db199SXin Li
237*9c5db199SXin Li    def set_usbc_mux(self, mux):
238*9c5db199SXin Li        """Sets PDTester usbc_mux.
239*9c5db199SXin Li
240*9c5db199SXin Li        @param mux: Specified mux state name.
241*9c5db199SXin Li        """
242*9c5db199SXin Li        if mux not in ['dp', 'usb']:
243*9c5db199SXin Li            raise PDTesterError('Invalid mux name: %s, '
244*9c5db199SXin Li                                'should be either \'dp\' or \'usb\'.' % mux)
245*9c5db199SXin Li        self.set(self.USBC_MUX, mux)
246*9c5db199SXin Li        time.sleep(self.USBC_COMMAND_DELAY)
247*9c5db199SXin Li
248*9c5db199SXin Li    def allow_pr_swap(self, allow):
249*9c5db199SXin Li        """Issue usbc_action prswap PDTester command
250*9c5db199SXin Li
251*9c5db199SXin Li        @param allow: a bool for ACK or NACK to PR_SWAP
252*9c5db199SXin Li                      command requested by DUT
253*9c5db199SXin Li        @returns value of prswap in PDTester FW
254*9c5db199SXin Li        """
255*9c5db199SXin Li        self.set(self.USBC_PRSWAP, int(allow))
256*9c5db199SXin Li
257*9c5db199SXin Li    def allow_dr_swap(self, allow):
258*9c5db199SXin Li        """Issue usbc_action drswap PDTester command
259*9c5db199SXin Li
260*9c5db199SXin Li        @param allow: a bool for ACK or NACK to DR_SWAP
261*9c5db199SXin Li                      command requested by DUT
262*9c5db199SXin Li        @returns value of drswap in PDTester FW
263*9c5db199SXin Li        """
264*9c5db199SXin Li        self.set(self.USBC_DRSWAP, int(allow))
265