Skip to content

Commit

Permalink
usbsdmux/FAST: Add support for outputs
Browse files Browse the repository at this point in the history
This change adds support for the two outputs on the USB-SD-Mux FAST.
The outputs are two open-drain channels that allow to drive external
signals low or even switch a (small) load.

Since this tooling does not store the state of the USB-SD-Mux this
means that we will always have to retrieve the state from the device
before we can modify any values.

To archive this we have to:

* On creation of the UsbSdMuxFast object we check if the device is already
  initialized. If not: The state is defined by pull-resistors on the board.
  Write this state into the I2C-GPIO expander without changing the state.
* Update the existing code, so that changing the state of the multiplexer
  keeps the state of the outputs in place.
* Add actual support to switch the output.
* Add support in the CLI.

Signed-off-by: Chris Fiege <[email protected]>
  • Loading branch information
SmithChart committed Nov 24, 2023
1 parent fee410b commit 69b284f
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 19 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ dist/
.tox/
envs/
.idea
venv/
36 changes: 33 additions & 3 deletions usbsdmux/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import errno
import sys

from .usbsdmux import UsbSdMuxFactory
from .usbsdmux import UsbSdMuxFactory, UsbSdMuxFast


def main():
Expand All @@ -36,10 +36,19 @@ def main():
"dut - set to dut mode\n"
"client - set to dut mode (alias for dut)\n"
"host - set to host mode\n"
"off - set to off mode",
choices=["get", "dut", "client", "host", "off"],
"off - set to off mode\n"
"gpio0 - manipulate gpio0\n"
"gpio1 - manipulate gpio1",
choices=["get", "dut", "client", "host", "off", "gpio0", "gpio1"],
type=str.lower,
)
parser.add_argument(
"gpio_action",
help="Action to perform on the GPIOs",
nargs="?",
default="",
choices=["", "low", "0", "high", "1", "get"],
)

# These arguments were previously used for the client/service
# based method to grant USB-SD-Mux access to non-root users.
Expand Down Expand Up @@ -68,6 +77,19 @@ def main():
print(f"Does {args.sg} really point to an USB-SD-Mux?")
sys.exit(1)
mode = args.mode.lower()
gpio_action = args.gpio_action.lower()

if mode in ("gpio0", "gpio1"):
# Preflight checks for GPIO mode
if not ctl.type == UsbSdMuxFactory.SD_MUX_TYPE_FAST:
print("This USB-SD-Mux does not support GPIOs.", file=sys.stderr)
exit(1)
if not gpio_action:
gpio_action = "get"
if mode == "gpio0":
gpio = UsbSdMuxFast.gpio0
elif mode == "gpio1":
gpio = UsbSdMuxFast.gpio1

try:
if mode == "off":
Expand All @@ -82,6 +104,14 @@ def main():
elif mode == "get":
print(ctl.get_mode())

elif mode in ("gpio0", "gpio1"):
if gpio_action == "get":
print(ctl.gpio_get(gpio))
elif gpio_action in ("low", "0"):
ctl.gpio_set_low(gpio)
elif gpio_action in ("high", "1"):
ctl.gpio_set_high(gpio)

except FileNotFoundError as fnfe:
print(fnfe, file=sys.stderr)
sys.exit(1)
Expand Down
19 changes: 9 additions & 10 deletions usbsdmux/i2c_gpio.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
import abc

# SPDX-License-Identifier: LGPL-2.1-or-later

Expand All @@ -25,10 +24,10 @@

class I2cGpio(ABC):
# Registers inside the supported GPIO expanders
_register_inputPort = 0x00
_register_outputPort = 0x01
_register_polarity = 0x02
_register_configuration = 0x03
register_inputPort = 0x00
register_outputPort = 0x01
register_polarity = 0x02
register_configuration = 0x03

# Values for the configuration register
_direction_output = 0
Expand Down Expand Up @@ -60,9 +59,9 @@ def set_pin_to_output(self, pins):
Arguments:
pins -- Combination of Pca9536.gpio_*
"""
direction = self.read_register(self._register_configuration)[0]
direction = self.read_register(self.register_configuration)[0]
direction = (direction & ~pins) & 0xFF
self._write_register(self._register_configuration, direction)
self._write_register(self.register_configuration, direction)

def set_pin_to_input(self, pins):
"""
Expand All @@ -71,9 +70,9 @@ def set_pin_to_input(self, pins):
Arguments:
pins -- Combination of Pca9536.gpio_*
"""
direction = self.read_register(self._register_configuration)[0]
direction = self.read_register(self.register_configuration)[0]
direction = direction | pins
self._write_register(self._register_configuration, direction)
self._write_register(self.register_configuration, direction)

def output_values(self, values: int, bitmask: int = 0xFF):
"""
Expand All @@ -87,7 +86,7 @@ def output_values(self, values: int, bitmask: int = 0xFF):

if bitmask == 0xFF:
# trivial case: Let's just write the value
self._write_register(self._register_outputPort, values)
self._write_register(self.register_outputPort, values)
else:
# complex case: Let's do a read-modify-write
val = self.read_register(self.register_outputPort, 1)[0]
Expand Down
58 changes: 52 additions & 6 deletions usbsdmux/usbsdmux.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def __init__(self, sg):
sg -- /dev/sg* to use
"""
self._pca = Pca9536(sg)
self.type = UsbSdMuxFactory.SD_MUX_TYPE_LEGACY

def get_mode(self):
"""
Expand Down Expand Up @@ -173,6 +174,9 @@ class UsbSdMuxFast:
_card_inserted = 0x00
_card_removed = Tca6408.gpio_3

gpio0 = Tca6408.gpio_4
gpio1 = Tca6408.gpio_5

# TODO: Add support for the GPIOs

def __init__(self, sg):
Expand All @@ -183,6 +187,23 @@ def __init__(self, sg):
sg -- /dev/sg* to use
"""
self._tca = Tca6408(sg)
self.type = UsbSdMuxFactory.SD_MUX_TYPE_FAST
self._assure_default_state()

def _assure_default_state(self):
# If the USB-SD-Mux has just been powered on, its default ("DUT") is defined by pull-resistors.
# If we now do a "read-modify-write" without taking into account the external default, we will
# lose this state.
# So let's check if the GPIO-expander is in Power-On-Reset defaults.
# If so, write the same state to the device - but with driven outputs.

if self._tca.read_register(self._tca.register_configuration)[0] == 0xFF:
# If all pins are still set to "input" we are in the default state.
# Let's set an output value that matches this configuration and set the relevant pins to output.
self._tca.output_values(self._DAT_enable | self._PWR_enable | self._select_DUT | self._card_removed)
self._tca.set_pin_to_output(
Tca6408.gpio_0 | Tca6408.gpio_1 | Tca6408.gpio_2 | Tca6408.gpio_3 | Tca6408.gpio_4 | Tca6408.gpio_5
)

def get_mode(self):
"""
Expand All @@ -208,9 +229,10 @@ def mode_disconnect(self, wait=True):
wait -- Command will block for some time until the voltage-supply of
the sd-card is known to be close to zero
"""
# Set the output registers to known values and activate them afterward
self._tca.output_values(self._DAT_disable | self._PWR_disable | self._select_HOST | self._card_removed)
self._tca.set_pin_to_output(Tca6408.gpio_0 | Tca6408.gpio_1 | Tca6408.gpio_2 | Tca6408.gpio_3)
self._tca.output_values(
values=self._DAT_disable | self._PWR_disable | self._select_HOST | self._card_removed,
bitmask=self._tca.gpio_0 | self._tca.gpio_1 | self._tca.gpio_2 | self._tca.gpio_3,
)

if wait:
time.sleep(1)
Expand All @@ -226,10 +248,16 @@ def mode_DUT(self, wait=True):

# switch selection to DUT first to prevent glitches on power and
# data-lines
self._tca.output_values(self._DAT_disable | self._PWR_disable | self._select_DUT | self._card_removed)
self._tca.output_values(
values=self._DAT_disable | self._PWR_disable | self._select_DUT | self._card_removed,
bitmask=self._tca.gpio_0 | self._tca.gpio_1 | self._tca.gpio_2 | self._tca.gpio_3,
)

# now connect data and power
self._tca.output_values(self._DAT_enable | self._PWR_enable | self._select_DUT | self._card_removed)
self._tca.output_values(
values=self._DAT_enable | self._PWR_enable | self._select_DUT | self._card_removed,
bitmask=self._tca.gpio_0 | self._tca.gpio_1 | self._tca.gpio_2 | self._tca.gpio_3,
)

def mode_host(self, wait=True):
"""
Expand All @@ -244,4 +272,22 @@ def mode_host(self, wait=True):
# Thus, we don't need to worry about glitches anymore.

# now connect data and power
self._tca.output_values(self._DAT_enable | self._PWR_enable | self._select_HOST | self._card_inserted)
self._tca.output_values(
values=self._DAT_enable | self._PWR_enable | self._select_HOST | self._card_inserted,
bitmask=self._tca.gpio_0 | self._tca.gpio_1 | self._tca.gpio_2 | self._tca.gpio_3,
)

def gpio_get(self, gpio):
"""
Reads the value of gpio and returns "high" or "low"
"""
val = self._tca.read_register(1)[0]
if val & gpio:
return "low"
return "high"

def gpio_set_high(self, gpio):
self._tca.register_reset_bit(self._tca.register_outputPort, gpio)

def gpio_set_low(self, gpio):
self._tca.register_set_bit(self._tca.register_outputPort, gpio)

0 comments on commit 69b284f

Please sign in to comment.