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