aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2017-05-02 11:37:59 -0700
committerMartin Braun <martin.braun@ettus.com>2017-12-22 15:03:52 -0800
commit9e57a7a71a282345d1eb49c0190b44fafff19b22 (patch)
treef4500d20d07e1f18bc61339f0e712196666b1912
parent2906db7528850040bb6f6fdd057b3488898c23aa (diff)
downloaduhd-9e57a7a71a282345d1eb49c0190b44fafff19b22.tar.gz
uhd-9e57a7a71a282345d1eb49c0190b44fafff19b22.tar.bz2
uhd-9e57a7a71a282345d1eb49c0190b44fafff19b22.zip
mpm: Added driver for sysfs-based gpios
-rw-r--r--mpm/python/usrp_mpm/CMakeLists.txt7
-rw-r--r--mpm/python/usrp_mpm/sysfs_gpio.py179
2 files changed, 183 insertions, 3 deletions
diff --git a/mpm/python/usrp_mpm/CMakeLists.txt b/mpm/python/usrp_mpm/CMakeLists.txt
index 4ce21ce11..44c70619f 100644
--- a/mpm/python/usrp_mpm/CMakeLists.txt
+++ b/mpm/python/usrp_mpm/CMakeLists.txt
@@ -21,12 +21,13 @@
SET(USRP_MPM_FILES ${USRP_MPM_FILES})
SET(USRP_MPM_TOP_FILES
${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
- ${CMAKE_CURRENT_SOURCE_DIR}/types.py
${CMAKE_CURRENT_SOURCE_DIR}/discovery.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/mpmlog.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/nijesdcore.py
${CMAKE_CURRENT_SOURCE_DIR}/rpc_server.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/sysfs_gpio.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/types.py
${CMAKE_CURRENT_SOURCE_DIR}/uio.py
- ${CMAKE_CURRENT_SOURCE_DIR}/nijesdcore.py
- ${CMAKE_CURRENT_SOURCE_DIR}/mpmlog.py
)
LIST(APPEND USRP_MPM_FILES ${USRP_MPM_TOP_FILES})
ADD_SUBDIRECTORY(periph_manager)
diff --git a/mpm/python/usrp_mpm/sysfs_gpio.py b/mpm/python/usrp_mpm/sysfs_gpio.py
new file mode 100644
index 000000000..fd06ad10d
--- /dev/null
+++ b/mpm/python/usrp_mpm/sysfs_gpio.py
@@ -0,0 +1,179 @@
+#
+# Copyright 2017 Ettus Research (National Instruments)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+Access to GPIOs mapped into the PS via sysfs
+"""
+
+import os
+import pyudev
+from .mpmlog import get_logger
+
+GPIO_SYSFS_BASE_DIR = '/sys/class/gpio'
+GPIO_SYSFS_LABELFILE = 'label'
+GPIO_SYSFS_VALUEFILE = 'value'
+
+def get_all_gpio_devs():
+ """
+ Returns a list of all GPIO chips available through sysfs. Will look
+ something like ['gpiochip882', 'gpiochip123', ...]
+ """
+ try:
+ context = pyudev.Context()
+ gpios = [device.sys_name.encode('ascii')
+ for device in context.list_devices(subsystem="gpio")
+ if os.path.exists(os.path.join( # udev probably has better ways to do this
+ GPIO_SYSFS_BASE_DIR,
+ device.sys_name.encode('ascii'),
+ GPIO_SYSFS_LABELFILE
+ ))
+ ]
+ return gpios
+ except OSError:
+ # Typically means GPIO not available, maybe no overlay
+ return []
+
+def get_gpio_map_info(gpio_dev):
+ """
+ Returns all the map info for a given GPIO device.
+ Example: If pio_dev is 'gpio882', it will list all files
+ in /sys/class/gpio/gpio882/ and create a dictionary with filenames
+ as keys and content as value. Subdirs are skipped.
+
+ Numbers are casted to numbers automatically. Strings remain strings.
+ """
+ map_info = {}
+ map_info_path = os.path.join(
+ GPIO_SYSFS_BASE_DIR, gpio_dev,
+ )
+ for info_file in os.listdir(map_info_path):
+ if not os.path.isfile(os.path.join(map_info_path, info_file)):
+ continue
+ map_info_value = open(os.path.join(map_info_path, info_file), 'r').read().strip()
+ try:
+ map_info[info_file] = int(map_info_value, 0)
+ except ValueError:
+ map_info[info_file] = map_info_value
+ # Manually add GPIO number
+ context = pyudev.Context()
+ map_info['sys_number'] = int(
+ pyudev.Devices.from_name(context, subsystem="gpio", sys_name=gpio_dev).sys_number
+ )
+ return map_info
+
+def find_gpio_device(label, logger=None):
+ """
+ Given a label, returns a tuple (uio_device, map_info).
+ uio_device is something like 'gpio882'. map_info is a dictionary with
+ information regarding the GPIO device read from the map info sysfs dir.
+ """
+ gpio_devices = get_all_gpio_devs()
+ if logger:
+ logger.trace("Found the following UIO devices: `{0}'".format(','.join(gpio_devices)))
+ for gpio_device in gpio_devices:
+ map_info = get_gpio_map_info(gpio_device)
+ if logger:
+ logger.trace("{0} has map info: {1}".format(gpio_device, map_info))
+ if map_info.get('label') == label:
+ if logger:
+ logger.trace("Device matches label: `{0}'".format(gpio_device))
+ return gpio_device, map_info
+ if logger:
+ logger.warning("Found no matching gpio device for label `{0}'".format(label))
+ return None, None
+
+class SysFSGPIO(object):
+ """
+ API for accessing GPIOs mapped into userland via sysfs
+ """
+
+ def __init__(self, label, use_mask, ddr):
+ assert (use_mask & ddr) == ddr
+ self.log = get_logger("SysFSGPIO")
+ self._label = label
+ self._use_mask = use_mask
+ self._ddr = ddr
+ self.log.trace("Generating SysFSGPIO object for label `{}'...".format(label))
+ self._gpio_dev, self._map_info = find_gpio_device(label, self.log)
+ if self._gpio_dev is None:
+ self.log.error("Could not find GPIO device with label `{}'.".format(label))
+ self.log.trace("GPIO base number is {}".format(self._map_info.get("sys_number")))
+ self._base_gpio = self._map_info.get("sys_number")
+ self.init(self._map_info['ngpio'], self._base_gpio, self._use_mask, self._ddr)
+
+ def init(self, n_gpio, base, use_mask, ddr):
+ """
+ Guarantees that all the devices are created accordingly
+
+ E.g., if use_mask & 0x1 is True, it makes sure that 'gpioXXX' is exported.
+ Also sets the DDRs.
+ """
+ gpio_list = [x for x in xrange(n_gpio) if (1<<x) & use_mask]
+ self.log.trace("Initializing {} GPIOs...".format(len(gpio_list)))
+ for gpio_idx in gpio_list:
+ gpio_num = base + gpio_idx
+ ddr_out = ddr & (1<<gpio_idx)
+ gpio_path = os.path.join(GPIO_SYSFS_BASE_DIR, 'gpio{}'.format(gpio_num))
+ if not os.path.exists(gpio_path):
+ self.log.trace("Creating GPIO path `{}'...".format(gpio_path))
+ open(os.path.join(GPIO_SYSFS_BASE_DIR, 'export'), 'w').write('{}'.format(gpio_num))
+ ddr_str = 'out' if ddr_out else 'in'
+ self.log.trace("On GPIO path `{}', setting DDR mode to {}.".format(gpio_path, ddr_str))
+ open(os.path.join(GPIO_SYSFS_BASE_DIR, gpio_path, 'direction'), 'w').write(ddr_str)
+
+ def set(self, gpio_idx, value=None):
+ """
+ Assert a GPIO at given index.
+
+ Note: The GPIO must be in the valid range, and it's DDR value must be
+ high (for "out").
+ """
+ if value is None:
+ value = 1
+ assert (1<<gpio_idx) & self._use_mask
+ assert (1<<gpio_idx) & self._ddr
+ gpio_num = self._base_gpio + gpio_idx
+ gpio_path = os.path.join(GPIO_SYSFS_BASE_DIR, 'gpio{}'.format(gpio_num))
+ value_path = os.path.join(gpio_path, GPIO_SYSFS_VALUEFILE)
+ self.log.trace("Writing value `{}' to `{}'...".format(value, value_path))
+ assert os.path.exists(value_path)
+ open(value_path, 'w').write('{}'.format(value))
+
+ def reset(self, gpio_idx):
+ """
+ Deassert a GPIO at given index.
+
+ Note: The GPIO must be in the valid range, and it's DDR value must be
+ high (for "out").
+ """
+ self.set(gpio_idx, value=0)
+
+ def get(self, gpio_idx):
+ """
+ Read back a GPIO at given index.
+
+ Note: The GPIO must be in the valid range, and it's DDR value must be
+ low (for "in").
+ """
+ assert (1<<gpio_idx) & self._use_mask
+ assert (1<<gpio_idx) & (~self._ddr)
+ gpio_num = self._base_gpio + gpio_idx
+ gpio_path = os.path.join(GPIO_SYSFS_BASE_DIR, 'gpio{}'.format(gpio_num))
+ value_path = os.path.join(gpio_path, GPIO_SYSFS_VALUEFILE)
+ self.log.trace("Writing value from `{}'...".format(value_path))
+ assert os.path.exists(value_path)
+ return int(open(value_path, 'r').read().strip())
+