blob: 2d48c34e966751b0241d203689d4e697d36f0527 [file] [log] [blame]
import collections
import logging
import os.path
PortId = collections.namedtuple('PortId', ['bus', 'port_number'])
GPIO_PATH = '/sys/class/gpio'
GUADO_CONTROLLER = 'INT3437:00'
# Mapping from bus ID and port number to the GPIO index.
_PORT_ID_TO_GPIO_INDEX_DICT = {
# On Guado, there are three gpios that control usb port power.
# These are offsets used to calculate GPIO index.
'guado': {
# Front ports
PortId(bus=1, port_number=2): 56, # Front left USB 2
PortId(bus=2, port_number=1): 56, # Front left USB 3
PortId(bus=1, port_number=3): 57, # Front right USB 2
PortId(bus=2, port_number=2): 57, # Front right USB 3
# Back ports (same GPIO is used for both ports)
PortId(bus=1, port_number=5): 47, # Back upper USB 2
PortId(bus=2, port_number=3): 47, # Back upper USB 3
PortId(bus=1, port_number=6): 47, # Back lower USB 2
PortId(bus=2, port_number=4): 47, # Back lower USB 3
},
# On Fizz, there are in total 5 usb ports and per port usb power
# is controlled by EC with user space command:
# ectool gpioset USBx_ENABLE 0/1 (x from 1 to 5).
'fizz': {
# USB 2 bus.
PortId(bus=1, port_number=3): 4, # Front right USB 2
PortId(bus=1, port_number=4): 5, # Front left USB 2
PortId(bus=1, port_number=5): 1, # Back left USB 2
PortId(bus=1, port_number=6): 2, # Back middle USB 2
PortId(bus=1, port_number=2): 3, # Back right USB 2
# USB 3 bus.
PortId(bus=2, port_number=3): 4, # Front right USB 3
PortId(bus=2, port_number=4): 5, # Front left USB 3
PortId(bus=2, port_number=5): 1, # Back left USB 3
PortId(bus=2, port_number=6): 2, # Back middle USB 3
PortId(bus=2, port_number=2): 3, # Back right USB 3
}
}
def _get_gpio_index(board, port_id):
return _PORT_ID_TO_GPIO_INDEX_DICT[board][port_id]
class UsbPortManager(object):
"""
Manages USB ports.
Can for example power cycle them.
"""
def __init__(self, host):
"""
Initializes with a host.
@param host a Host object.
"""
self._host = host
def set_port_power(self, port_ids, power_on):
"""
Turns on or off power to the USB port for peripheral devices.
@param port_ids Iterable of PortId instances (i.e. bus, port_number
tuples) to set power for.
@param power_on If true, turns power on. If false, turns power off.
"""
for port_id in port_ids:
gpio_index = _get_gpio_index(self._get_board(), port_id)
self._set_gpio_power(self._get_board(), gpio_index, power_on)
def _find_gpio_base_index(self, expected_controller):
"""
Finds the gpiochip* base index using the expected controller.
If `cat /sys/class/gpio/gpiochip<N>/label` has the expected controller, return <N>
@param expected_controller The controller to match to return gpiochip<N>/base
"""
gpiochips = self._run(
'ls -d /sys/class/gpio/gpiochip*').stdout.strip().split('\n')
if not gpiochips:
raise ValueError('No gpiochips found')
for gpiochip in gpiochips:
logging.debug('Checking gpiochip path "%s" for controller %s',
gpiochip, expected_controller)
gpiochip_label = os.path.join(gpiochip, 'label')
gpiochip_controller = self._run(
'cat {}'.format(gpiochip_label)).stdout.strip()
if gpiochip_controller == expected_controller:
gpiochip_base = os.path.join(gpiochip, 'base')
gpiochip_base_index = self._run(
'cat {}'.format(gpiochip_base)).stdout.strip()
return int(gpiochip_base_index)
raise ValueError('Expected controller not found')
def _get_board(self):
# host.get_board() adds 'board: ' in front of the board name
return self._host.get_board().split(':')[1].strip()
def _set_gpio_power_guado(self, gpio_idx, power_on):
"""
Turns on or off the power for a specific GPIO on board Guado.
@param gpio_idx The *offset* of the gpio to set the power for, added to the base.
@param power_on If True, powers on the GPIO. If False, powers it off.
"""
# First, we need to find the gpio base
gpio_base_index = self._find_gpio_base_index(GUADO_CONTROLLER)
# Once base is found, calculate index
gpio_index = gpio_base_index + gpio_idx
logging.debug('Using gpio index: "%s"', gpio_index)
gpio_path = '/sys/class/gpio/gpio{}'.format(gpio_index)
did_export = False
if not self._host.path_exists(gpio_path):
did_export = True
self._run('echo {} > /sys/class/gpio/export'.format(
gpio_index))
try:
self._run('echo out > {}/direction'.format(gpio_path))
value_string = '1' if power_on else '0'
self._run('echo {} > {}/value'.format(
value_string, gpio_path))
finally:
if did_export:
self._run('echo {} > /sys/class/gpio/unexport'.format(
gpio_index))
def _set_gpio_power_fizz(self, gpio_idx, power_on):
"""
Turns on or off the power for a specific GPIO on board Fizz.
@param gpio_idx The index of the gpio to set the power for.
@param power_on If True, powers on the GPIO. If False, powers it off.
"""
value_string = '1' if power_on else '0'
cmd = 'ectool gpioset USB{}_ENABLE {}'.format(gpio_idx,
value_string)
self._run(cmd)
def _set_gpio_power(self, board, gpio_index, power_on):
"""
Turns on or off the power for a specific GPIO.
@param board Board type. Currently support: Guado, Fizz.
@param gpio_idx The index of the gpio to set the power for.
@param power_on If True, powers on the GPIO. If False, powers it off.
"""
if board == 'guado':
self._set_gpio_power_guado(gpio_index, power_on)
elif board == 'fizz':
self._set_gpio_power_fizz(gpio_index, power_on)
else:
raise ValueError('Unsupported board type {}.'.format(board))
def _run(self, command):
logging.debug('Running: "%s"', command)
res = self._host.run(command)
logging.debug('Result: "%s"', res)
return res