1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
|
#
# Copyright 2017 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Access to GPIOs mapped into the PS via sysfs
"""
import os
from builtins import object
import pyudev
from usrp_mpm.mpmlog import get_logger
GPIO_SYSFS_BASE_DIR = '/sys/class/gpio'
GPIO_SYSFS_VALUEFILE = 'value'
def get_all_gpio_devs(parent_dev=None):
"""
Returns a list of all GPIO chips available through sysfs. Will look
something like ['gpiochip882', 'gpiochip123', ...]
If there are multiple devices with the same label (example: daughterboards
may have a single label for a shared component), a parent device needs to
be provided to disambiguate.
Arguments:
parent_dev -- A parent udev device. If this is provided, only GPIO devices
which are a child of the parent device are returned.
Example:
>>> parent_dev = pyudev.Devices.from_sys_path(
pyudev.Context(), '/sys/class/i2c-adapter/i2c-10')
>>> get_all_gpio_devs(parent_dev)
"""
try:
context = pyudev.Context()
gpios = [device.sys_name
for device in context.list_devices(
subsystem="gpio").match_parent(parent_dev)
if device.device_number == 0
]
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 get_map_data(gpio_dev, path, logger=None):
"""
Returns the value of the file found at the given path within a gpio_dev.
Example: If gpio_dev is 'gpio882' and path is 'device/of_node/name'
the value found at '/sys/class/gpio/gpio882/device/of_node/name' will
be returned.
Numbers are casted to numbers automatically. Strings remain strings.
"""
map_path = os.path.join(GPIO_SYSFS_BASE_DIR, path, gpio_dev)
if not os.path.isfile(map_path):
if logger:
logger.trace("Couldn't find a device tree file to match: `{0}'".format(map_path))
return None
map_info_value = open(map_path, 'r').read().strip().rstrip('\x00')
if logger:
logger.trace("File at `{0}' has value `{1}'".format(map_path, map_info_value))
try:
map_info_value = int(map_info_value, 0)
except ValueError:
pass
# Manually add GPIO number
return map_info_value
def find_gpio_device(identifiers, parent_dev=None, logger=None):
"""
Given identifiers, returns a tuple (uio_device, map_info).
identifiers is a dictionary of paths as keys and expected values as values
(e.g. {"label": "tca6404"}).
uio_device is something like 'gpio882'.
map_info is a dictionary with information regarding the GPIO device read
from the map info sysfs dir.
"""
def id_dict_compare(identifiers, gpio_dev, logger=None):
"""
Checks that a gpio_dev matches the path value pairs specified in
identifiers.
Returns True if the gpio_dev matches all path value pairs, otherwise
returns False
"""
for k in identifiers:
if get_map_data(k, gpio_dev, logger) != identifiers[k]:
return False
return True
gpio_devices = get_all_gpio_devs(parent_dev)
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 id_dict_compare(identifiers, gpio_device, logger):
if logger:
logger.trace("Device matches identifiers: `{0}'".format(gpio_device))
return gpio_device, map_info
if logger:
logger.warning("Found no matching gpio device for identifiers `{0}'".format(identifiers))
return None, None
class SysFSGPIO(object):
"""
API for accessing GPIOs mapped into userland via sysfs
"""
def __init__(self, identifiers, use_mask, ddr, init_value=0, parent_dev=None):
assert (use_mask & ddr) == ddr
self.log = get_logger("SysFSGPIO")
self._identifiers = identifiers
self._use_mask = use_mask
self._ddr = ddr
self._init_value = init_value
self._out_value = 0
self.log.trace("Generating SysFSGPIO object for identifiers `{}'..."
.format(identifiers))
self._gpio_dev, self._map_info = \
find_gpio_device(identifiers, parent_dev, self.log)
if self._gpio_dev is None:
error_msg = \
"Could not find GPIO device with identifiers `{}'.".format(identifiers)
self.log.error(error_msg)
raise RuntimeError(error_msg)
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,
self._init_value)
def init(self, n_gpio, base, use_mask, ddr, init_value=0):
"""
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 range(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)
ini_v = init_value & (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'
ddr_str = 'high' if ini_v else ddr_str
if ini_v and ddr_out:
self._out_value |= 1 << gpio_idx
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
assert int(value) in [0, 1]
value = int(value)
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))
if value:
self._out_value |= 1 << gpio_idx
else:
self._out_value &= ~(1 << gpio_idx)
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. If it's DDR value is
low (for "in") then the register value is read from a local variable.
"""
assert (1<<gpio_idx) & self._use_mask
if (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)
assert os.path.exists(value_path)
read_value = int(open(value_path, 'r').read().strip())
self.log.trace("Reading value {} from `{}'...".format(read_value, value_path))
else:
read_value = 1 if self._out_value & (1 << gpio_idx) else 0
self.log.trace("Reading value {} from local var".format(read_value))
return read_value
class GPIOBank:
"""
Usability / convenience wrapper for GPIO banks accessed by SysFSGPIO
This class is useful when:
- Accessing a GPIO bank that is hanging off of a SysFSGPIO node
- All pins in this bank are consecutive pins on the SysFSGPIO node
- The pins can be at an offset (i.e., do not have to start at pin 0 of the
SysFSGPIO node)
- The pins might have to be written/read as a whole
For example, assume that a device has 8 GPIO pins, starting at pin 10. The
user wants to read all pins at once as a single byte:
>>> gpio_bank = GPIOBank(
label_dict, # See SysFSGPIO for this parameter
10, # Pin offset
0xFF, # 8 pins. Must be consecutive ones!
0x00) # All pins are readable
>>> if gpio_bank.get_all() == 3:
print("Pins 0 and 1 are high!")
"""
def __init__(self, uio_identifiers, offset, usemask, ddr):
self._gpiosize = bin(usemask).count("1")
# Make sure the pins are all one:
assert (1 << self._gpiosize) == usemask+1
self._offset = offset
self._ddr = ddr
self._usemask = usemask
self._gpios = SysFSGPIO(
uio_identifiers,
self._usemask << self._offset,
self._ddr << self._offset
)
def set(self, index, value=None):
"""
Set a pin by index
"""
assert index in range(self._gpiosize)
self._gpios.set(self._offset + index, value)
def reset_all(self):
"""
Clear all pins
"""
for i in range(self._gpiosize):
if self._ddr & i:
self._gpios.reset(self._offset+i)
def reset(self, index):
"""
Clear a pin by index
Read back a pin by index. See also SysFSGPIO.reset(). The DDR value for
this pin must be high.
"""
assert index in range(self._gpiosize)
self._gpios.reset(self._offset + index)
def get_all(self):
"""
Read back all pins. Pins with a DDR value of 1 ("output") are left zero.
"""
result = 0
for i in range(self._gpiosize):
if not (1<<i) & self._ddr:
value = self._gpios.get(self._offset + i)
result = (result << 1) | value
return result
def get(self, index):
"""
Read back a pin by index. See also SysFSGPIO.get()
"""
assert index in range(self._gpiosize)
return self._gpios.get(self._offset + index)
|