# Lint as: python2, python3 # Copyright 2015 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import logging import re import time from autotest_lib.client.bin import utils from autotest_lib.server.cros.servo import chrome_ec class PDTesterError(Exception): """Error object for PDTester""" pass class PDTester(chrome_ec.ChromeEC): """Manages control of a PDTester hardware. PDTester is a general term for hardware developed to aid in USB type-C debug and control of various type C host devices. It can be either a Plankton board or a Servo v4 board. We control the PDTester board via the UART and the Servod interfaces. PDTester provides many interfaces that access the hardware. It can also be passed into the PDConsoleUtils as a console which then provides methods to access the pd console. This class is to abstract these interfaces. """ # USB charging command delays in seconds. USBC_COMMAND_DELAY = 0.5 # PDTester USBC commands. USBC_ROLE= 'usbc_role' # TODO(b:140256624): deprecate by USBC_PR USBC_PR= 'usbc_pr' USBC_MUX = 'usbc_mux' RE_USBC_ROLE_VOLTAGE = r'src(\d+)v' USBC_SRC_CAPS = 'ada_srccaps' USBC_CHARGING_VOLTAGES = { 0: 'sink', 5: 'src5v', 9: 'src9v', 10: 'src10v', 12: 'src12v', 15: 'src15v', 20: 'src20v'} # TODO(b:140256624): deprecate by USBC_CHARGING_VOLTAGES USBC_CHARGING_VOLTAGES_LEGACY = { 0: 'sink', 5: 'src5v', 12: 'src12v', 20: 'src20v'} USBC_MAX_VOLTAGE = 20 VBUS_VOLTAGE_MV = 'vbus_voltage' VBUS_CURRENT_MA = 'vbus_current' VBUS_POWER_MW = 'vbus_power' # USBC PD states. USBC_PD_STATES = { 'sink': 'SNK_READY', 'source': 'SRC_READY'} POLL_STATE_SECS = 2 def __init__(self, servo, servod_proxy): """Initialize and keep the servo object. @param servo: A Servo object @param servod_proxy: Servod proxy for pdtester host """ self.servo_type = servo.get_servo_version() if 'servo_v4' in self.servo_type: uart_prefix = 'servo_v4_uart' else: uart_prefix = 'ec_uart' super(PDTester, self).__init__(servo, uart_prefix) # save servod proxy for methods that access PDTester servod self._server = servod_proxy self.init_hardware() def init_hardware(self): """Initializes PDTester hardware.""" if self.servo_type == 'plankton': if not int(self.get('debug_usb_sel')): raise PDTesterError('debug_usb_sel (SW3) should be ON!! ' 'Please use CN15 to connect Plankton.') self.set('typec_to_hub_sw', '0') self.set('usb2_mux_sw', '1') self.set('usb_dn_pwren', 'on') def set(self, control_name, value): """Sets the value of a control using servod. @param control_name: pdtester servo control item @param value: value to set pdtester servo control item """ assert control_name self._server.set(control_name, value) def get(self, control_name): """Gets the value of a control from servod. @param control_name: pdtester servo control item """ assert control_name return self._server.get(control_name) @property def vbus_voltage(self): """Gets PDTester VBUS voltage in volts.""" return float(self.get(self.VBUS_VOLTAGE_MV)) / 1000.0 @property def vbus_current(self): """Gets PDTester VBUS current in amps.""" return float(self.get(self.VBUS_CURRENT_MA)) / 1000.0 @property def vbus_power(self): """Gets PDTester charging power in watts.""" return float(self.get(self.VBUS_POWER_MW)) / 1000.0 def get_adapter_source_caps(self): """Gets a list of SourceCap Tuples in mV/mA.""" try: res = self.get(self.USBC_SRC_CAPS) except: raise PDTesterError('Unsupported servov4 command(%s). ' 'Maybe firmware or servod too old? ' 'sudo servo_updater -b servo_v4; ' 'sudo emerge hdctools' % self.USBC_SRC_CAPS) srccaps = [] for pdo_str in res: m = re.match(r'\d: (\d+)mV/(\d+)mA', pdo_str) srccaps.append((int(m.group(1)), int(m.group(2)))) return srccaps def get_charging_voltages(self): """Gets the lists of available charging voltages of the adapter.""" try: srccaps = self.get_adapter_source_caps() except PDTesterError: # htctools and servov4 is not updated, fallback to the old path. logging.warn('hdctools or servov4 firmware too old, fallback to ' 'fixed charging voltages.') return list(self.USBC_CHARGING_VOLTAGES_LEGACY.keys()) # insert 0 voltage for sink vols = [0] for pdo in srccaps: vols.append(pdo[0]/1000) return vols def charge(self, voltage): """Sets PDTester to provide power at specific voltage. @param voltage: Specified charging voltage in volts. """ charging_voltages = self.get_charging_voltages() if voltage not in charging_voltages: logging.warning('Unsupported voltage(%s) of the adapter. ' 'Maybe firmware or servod too old? ' 'sudo servo_updater -b servo_v4; ' 'sudo emerge hdctools' % voltage) try: self.set(self.USBC_PR, self.USBC_CHARGING_VOLTAGES[voltage]) except: self.set(self.USBC_ROLE, self.USBC_CHARGING_VOLTAGES_LEGACY[voltage]) time.sleep(self.USBC_COMMAND_DELAY) @property def charging_voltage(self): """Gets current charging voltage.""" try: usbc_pr = self.get(self.USBC_PR) except: logging.warn('Unsupported control(%s). ' 'Maybe firmware or servod too old? ' 'sudo servo_updater -b servo_v4; ' 'sudo emerge hdctools' % self.USBC_PR) usbc_pr = self.get(self.USBC_ROLE) m = re.match(self.RE_USBC_ROLE_VOLTAGE, usbc_pr) if m: return int(m.group(1)) if usbc_pr == self.USBC_CHARGING_VOLTAGES[0]: return 0 raise PDTesterError('Invalid USBC power role: %s' % usbc_pr) def poll_pd_state(self, state): """Polls until PDTester pd goes to the specific state. @param state: Specified pd state name. """ if state not in self.USBC_PD_STATES: raise PDTesterError('Invalid state name: %s' % state) utils.poll_for_condition( lambda: self.get('pd_state') == self.USBC_PD_STATES[state], exception=utils.TimeoutError('PDTester not in %s state ' 'after %s seconds.' % (self.USBC_PD_STATES[state], self.POLL_STATE_SECS)), timeout=self.POLL_STATE_SECS) def set_usbc_mux(self, mux): """Sets PDTester usbc_mux. @param mux: Specified mux state name. """ if mux not in ['dp', 'usb']: raise PDTesterError('Invalid mux name: %s, ' 'should be either \'dp\' or \'usb\'.' % mux) self.set(self.USBC_MUX, mux) time.sleep(self.USBC_COMMAND_DELAY)