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