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