From 69b284f421809bdc39ed73efaa86b31ca9eb75b6 Mon Sep 17 00:00:00 2001 From: Chris Fiege Date: Mon, 6 Nov 2023 12:26:59 +0100 Subject: [PATCH] usbsdmux/FAST: Add support for outputs 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 --- .gitignore | 1 + usbsdmux/__main__.py | 36 ++++++++++++++++++++++++--- usbsdmux/i2c_gpio.py | 19 +++++++-------- usbsdmux/usbsdmux.py | 58 +++++++++++++++++++++++++++++++++++++++----- 4 files changed, 95 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 8e50b11..a68ad5a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ dist/ .tox/ envs/ .idea +venv/ diff --git a/usbsdmux/__main__.py b/usbsdmux/__main__.py index 2519c25..0286b06 100755 --- a/usbsdmux/__main__.py +++ b/usbsdmux/__main__.py @@ -22,7 +22,7 @@ import errno import sys -from .usbsdmux import UsbSdMuxFactory +from .usbsdmux import UsbSdMuxFactory, UsbSdMuxFast def main(): @@ -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. @@ -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": @@ -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) diff --git a/usbsdmux/i2c_gpio.py b/usbsdmux/i2c_gpio.py index cff1eac..d252df9 100644 --- a/usbsdmux/i2c_gpio.py +++ b/usbsdmux/i2c_gpio.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import abc # SPDX-License-Identifier: LGPL-2.1-or-later @@ -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 @@ -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): """ @@ -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): """ @@ -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] diff --git a/usbsdmux/usbsdmux.py b/usbsdmux/usbsdmux.py index 2446bb6..8425226 100644 --- a/usbsdmux/usbsdmux.py +++ b/usbsdmux/usbsdmux.py @@ -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): """ @@ -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): @@ -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): """ @@ -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) @@ -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): """ @@ -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)