• 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_ROLE= 'usbc_role' # TODO(b:140256624): deprecate by USBC_PR
37    USBC_PR= 'usbc_pr'
38    USBC_MUX = 'usbc_mux'
39    RE_USBC_ROLE_VOLTAGE = r'src(\d+)v'
40    USBC_SRC_CAPS = 'ada_srccaps'
41    USBC_CHARGING_VOLTAGES = {
42        0: 'sink',
43        5: 'src5v',
44        9: 'src9v',
45        10: 'src10v',
46        12: 'src12v',
47        15: 'src15v',
48        20: 'src20v'}
49    # TODO(b:140256624): deprecate by USBC_CHARGING_VOLTAGES
50    USBC_CHARGING_VOLTAGES_LEGACY = {
51        0: 'sink',
52        5: 'src5v',
53        12: 'src12v',
54        20: 'src20v'}
55    USBC_MAX_VOLTAGE = 20
56    VBUS_VOLTAGE_MV = 'vbus_voltage'
57    VBUS_CURRENT_MA = 'vbus_current'
58    VBUS_POWER_MW = 'vbus_power'
59    # USBC PD states.
60    USBC_PD_STATES = {
61        'sink': 'SNK_READY',
62        'source': 'SRC_READY'}
63    POLL_STATE_SECS = 2
64
65    def __init__(self, servo, servod_proxy):
66        """Initialize and keep the servo object.
67
68        @param servo: A Servo object
69        @param servod_proxy: Servod proxy for pdtester host
70        """
71        self.servo_type = servo.get_servo_version()
72        if 'servo_v4' in self.servo_type:
73            uart_prefix = 'servo_v4_uart'
74        else:
75            uart_prefix = 'ec_uart'
76
77        super(PDTester, self).__init__(servo, uart_prefix)
78        # save servod proxy for methods that access PDTester servod
79        self._server = servod_proxy
80        self.init_hardware()
81
82
83    def init_hardware(self):
84        """Initializes PDTester hardware."""
85        if self.servo_type == 'plankton':
86            if not int(self.get('debug_usb_sel')):
87                raise PDTesterError('debug_usb_sel (SW3) should be ON!! '
88                                    'Please use CN15 to connect Plankton.')
89            self.set('typec_to_hub_sw', '0')
90            self.set('usb2_mux_sw', '1')
91            self.set('usb_dn_pwren', 'on')
92
93
94    def set(self, control_name, value):
95        """Sets the value of a control using servod.
96
97        @param control_name: pdtester servo control item
98        @param value: value to set pdtester servo control item
99        """
100        assert control_name
101        self._server.set(control_name, value)
102
103
104    def get(self, control_name):
105        """Gets the value of a control from servod.
106
107        @param control_name: pdtester servo control item
108        """
109        assert control_name
110        return self._server.get(control_name)
111
112
113    @property
114    def vbus_voltage(self):
115        """Gets PDTester VBUS voltage in volts."""
116        return float(self.get(self.VBUS_VOLTAGE_MV)) / 1000.0
117
118
119    @property
120    def vbus_current(self):
121        """Gets PDTester VBUS current in amps."""
122        return float(self.get(self.VBUS_CURRENT_MA)) / 1000.0
123
124
125    @property
126    def vbus_power(self):
127        """Gets PDTester charging power in watts."""
128        return float(self.get(self.VBUS_POWER_MW)) / 1000.0
129
130    def get_adapter_source_caps(self):
131        """Gets a list of SourceCap Tuples in mV/mA."""
132        try:
133            res = self.get(self.USBC_SRC_CAPS)
134        except:
135            raise PDTesterError('Unsupported servov4 command(%s). '
136                                'Maybe firmware or servod too old? '
137                                'sudo servo_updater -b servo_v4; '
138                                'sudo emerge hdctools' % self.USBC_SRC_CAPS)
139
140        srccaps = []
141        for pdo_str in res:
142            m = re.match(r'\d: (\d+)mV/(\d+)mA', pdo_str)
143            srccaps.append((int(m.group(1)), int(m.group(2))))
144        return srccaps
145
146    def get_charging_voltages(self):
147        """Gets the lists of available charging voltages of the adapter."""
148        try:
149            srccaps = self.get_adapter_source_caps()
150        except PDTesterError:
151            # htctools and servov4 is not updated, fallback to the old path.
152            logging.warn('hdctools or servov4 firmware too old, fallback to '
153                         'fixed charging voltages.')
154            return list(self.USBC_CHARGING_VOLTAGES_LEGACY.keys())
155
156        # insert 0 voltage for sink
157        vols = [0]
158        for pdo in srccaps:
159            vols.append(pdo[0]/1000)
160        return vols
161
162    def charge(self, voltage):
163        """Sets PDTester to provide power at specific voltage.
164
165        @param voltage: Specified charging voltage in volts.
166        """
167        charging_voltages = self.get_charging_voltages()
168        if voltage not in charging_voltages:
169            logging.warning('Unsupported voltage(%s) of the adapter. '
170                            'Maybe firmware or servod too old? '
171                            'sudo servo_updater -b servo_v4; '
172                            'sudo emerge hdctools' % voltage)
173
174        try:
175            self.set(self.USBC_PR, self.USBC_CHARGING_VOLTAGES[voltage])
176        except:
177            self.set(self.USBC_ROLE,
178                     self.USBC_CHARGING_VOLTAGES_LEGACY[voltage])
179        time.sleep(self.USBC_COMMAND_DELAY)
180
181    @property
182    def charging_voltage(self):
183        """Gets current charging voltage."""
184        try:
185            usbc_pr = self.get(self.USBC_PR)
186        except:
187            logging.warn('Unsupported control(%s). '
188                         'Maybe firmware or servod too old? '
189                         'sudo servo_updater -b servo_v4; '
190                         'sudo emerge hdctools' % self.USBC_PR)
191            usbc_pr = self.get(self.USBC_ROLE)
192        m = re.match(self.RE_USBC_ROLE_VOLTAGE, usbc_pr)
193        if m:
194            return int(m.group(1))
195
196        if usbc_pr == self.USBC_CHARGING_VOLTAGES[0]:
197            return 0
198
199        raise PDTesterError('Invalid USBC power role: %s' % usbc_pr)
200
201
202    def poll_pd_state(self, state):
203        """Polls until PDTester pd goes to the specific state.
204
205        @param state: Specified pd state name.
206        """
207        if state not in self.USBC_PD_STATES:
208            raise PDTesterError('Invalid state name: %s' % state)
209        utils.poll_for_condition(
210            lambda: self.get('pd_state') == self.USBC_PD_STATES[state],
211            exception=utils.TimeoutError('PDTester not in %s state '
212                                         'after %s seconds.' %
213                                         (self.USBC_PD_STATES[state],
214                                          self.POLL_STATE_SECS)),
215            timeout=self.POLL_STATE_SECS)
216
217
218    def set_usbc_mux(self, mux):
219        """Sets PDTester usbc_mux.
220
221        @param mux: Specified mux state name.
222        """
223        if mux not in ['dp', 'usb']:
224            raise PDTesterError('Invalid mux name: %s, '
225                                'should be either \'dp\' or \'usb\'.' % mux)
226        self.set(self.USBC_MUX, mux)
227        time.sleep(self.USBC_COMMAND_DELAY)
228