Skip to content

Commit

Permalink
[api] Move GPIO code from modm to here
Browse files Browse the repository at this point in the history
  • Loading branch information
salkinium committed Feb 20, 2021
1 parent 068b715 commit a7f5782
Show file tree
Hide file tree
Showing 9 changed files with 365 additions and 40 deletions.
10 changes: 9 additions & 1 deletion modm_devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@
from . import device_identifier
from . import device
from . import parser
from . import stm32

from .pkg import naturalkey
from .exception import ParserException

__all__ = ['exception', 'device_file', 'device_identifier', 'device', 'parser', 'pkg']
__all__ = [
'exception',
'device_file',
'device_identifier',
'device',
'parser',
'pkg'
]

__version__ = "0.2.8"
17 changes: 17 additions & 0 deletions modm_devices/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import functools

class cached_property(object):
def __init__(self, func):
self.__doc__ = getattr(func, "__doc__")
self.func = func

def __get__(self, obj, cls):
if obj is None:
return self
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value

cached_function = functools.lru_cache(None)
77 changes: 41 additions & 36 deletions modm_devices/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,63 +10,68 @@

from .exception import ParserException
from .device_identifier import DeviceIdentifier

from .driver import Driver
from .cache import *
import fnmatch

class Device:
def __init__(self,
identifier: DeviceIdentifier,
device_file):
self._identifier = identifier.copy()
self.naming_schema = identifier.naming_schema
self.partname = identifier.string
self.device_file = device_file

self._device_file = device_file
self.__properties = None

@property
def _properties(self):
if self.__properties is None:
self.__properties = self.device_file.get_properties(self._identifier)
self.__properties = self._device_file.get_properties(self._identifier)
return self.__properties

@property
def identifier(self):
return self._identifier

def get_all_drivers(self, name):
parts = name.split(":")
def _find_drivers(self, *patterns):
results = []
for pattern in patterns:
parts = pattern.split(":")

if len(parts) == 1:
results = [d for d in self._properties["driver"] if d["name"] == parts[0]]
elif len(parts) == 2:
find_all = (parts[1][-1] == '*')
for driver in self._properties["driver"]:
if driver["name"] == parts[0] and \
((find_all and driver["type"].startswith(parts[1][:-1])) or
(not find_all and driver["type"] == parts[1])):
results.append(driver)
else:
raise ParserException("Invalid driver name '{}'. "
"The name must contain no or one ':' to "
"separate type and name.".format(name))
if len(parts) == 1:
results.extend(d for d in self._properties["driver"]
if fnmatch.fnmatch(d["name"], parts[0]))
elif len(parts) == 2:
results.extend(d for d in self._properties["driver"]
if (fnmatch.fnmatch(d["name"], parts[0]) and
fnmatch.fnmatch(d["type"], parts[1])))
else:
raise ParserException("Invalid driver pattern '{}'. "
"The name must contain no or one ':' to "
"separate `name:type` pattern.".format(parts))

return results

def get_driver(self, name):
results = self.get_all_drivers(name)
def _find_first_driver(self, *patterns):
results = self._find_drivers(*patterns)
return results[0] if len(results) else None

def has_driver(self, name, type: list = []):
if len(type) == 0:
return self.get_driver(name) is not None
@property
def did(self):
return self._identifier

def driver(self, name):
return Driver(self, self._find_first_driver(name))

def drivers(self, *names):
return [Driver(self, d) for d in self._find_drivers(*names)]

if ':' in name:
raise ParserException("Invalid driver name '{}'. "
"The name must contain no ':' when using the "
"compatible argument.".format(name))
def has_driver(self, *names):
return len(self._find_drivers(*names))

return any(self.get_driver(name + ':' + c) is not None for c in type)
# Deprecated stuff
def get_driver(self, pattern):
return self._find_first_driver(pattern)

def __str__(self):
return self.partname
def get_all_drivers(self, *patterns):
return self._find_drivers(*patterns)

@property
def identifier(self):
return self.did
11 changes: 8 additions & 3 deletions modm_devices/device_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from collections import defaultdict

from .device import Device
from .stm32.device import Stm32Device
from .device_identifier import DeviceIdentifier
from .device_identifier import MultiDeviceIdentifier
from .access import read_only
Expand Down Expand Up @@ -47,10 +48,14 @@ def get_devices(self):
valid_devices = [node.text for node in device_node.iterfind(self._VALID_DEVICE)]
devices = identifiers
if len(invalid_devices):
devices = [did for did in devices if did.string not in invalid_devices]
devices = (did for did in devices if did.string not in invalid_devices)
if len(valid_devices):
devices = [did for did in devices if did.string in valid_devices]
return [Device(did, self) for did in devices]
devices = (did for did in devices if did.string in valid_devices)
def build_device(did, device_file):
if did.platform == "stm32":
return Stm32Device(did, device_file)
return Device(did, device_file)
return [build_device(did, self) for did in devices]

@staticmethod
def is_valid(node, identifier: DeviceIdentifier):
Expand Down
44 changes: 44 additions & 0 deletions modm_devices/driver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020, Niklas Hauser
# All rights reserved.

from collections import defaultdict
from .cache import *

class Instance:
def __init__(self, driver, instance):
self._instance = instance
self.driver = driver
self.name = self._instance["name"]
self.number = int(self.name) if self.name.isdigit() else self.name

def features(self, default=[]):
feats = self.driver.features()
feats.extend(self._instance.get("feature", []))
return feats if len(feats) else default

def __str__(self):
return self.name


class Driver:
def __init__(self, device, driver):
self._driver = driver
self.device = device
self.name = self._driver["name"]
self.type = self._driver["type"]

def instances(self, default=[]):
if "instance" in self._driver:
return [Instance(self, i) for i in self._driver["instance"]]
return default

def features(self, default=[]):
if "feature" in self._driver:
return list(self._driver["feature"])
return default

def __str__(self):
return self.name
1 change: 1 addition & 0 deletions modm_devices/stm32/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import device
54 changes: 54 additions & 0 deletions modm_devices/stm32/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020, Niklas Hauser
# All rights reserved.

from ..cache import cached_property
from ..driver import Driver
from collections import defaultdict

class DriverCore(Driver):
def __init__(self, device):
Driver.__init__(self, device, device._find_first_driver("core"))


def vectors(self, filterfn=None):
vecs = (v["name"] for v in self._driver["vector"])
if filterfn is not None:
vecs = filter(filterfn, vecs)
return vecs


def instance_irq_map(self, name):
"""
:return: a map from int(instance) to str(name) interrupt starting with {name}.
"""
vector_map = defaultdict(list)
for vector in self.vectors(lambda v: v.startswith(name)):
vrange = sorted(int(d) for d in vector[len(name):].split("_") if d.isdigit())
if len(vrange) == 2:
vrange = list(range(vrange[0], vrange[1]+1))
for num in vrange:
# if num in vector_map:
# raise ValueError("Instance '{}' already in '{}' map!".format(str(num), name))
vector_map[num].append(vector)
return vector_map


def shared_irqs(self, name):
"""
:return: a map from str(name) to range(instances) >= 2 for interrupts starting with {name}.
"""
vector_range = {}
for vector in self.vectors(lambda v: v.startswith(name)):
vrange = sorted(int(d) for d in vector[len(name):].split("_") if d.isdigit())
if len(vrange) <= 1:
continue;
if len(vrange) == 2:
vrange = list(range(vrange[0], vrange[1]+1))
if vector in vector_range:
raise ValueError("Vector '{}' already in '{}' map!".format(str(vector), name))
vector_range[vector] = vrange
return vector_range

49 changes: 49 additions & 0 deletions modm_devices/stm32/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016, Fabian Greif
# Copyright (c) 2016, Niklas Hauser
# All rights reserved.

from .gpio import DriverGpio
from .core import DriverCore
from ..device import Device
from ..cache import cached_property
from ..access import copy_keys


class Stm32Device(Device):
def __init__(self, identifier, device_file):
Device.__init__(self, identifier, device_file)

@cached_property
def gpio(self):
return DriverGpio(self)

@cached_property
def core(self):
return DriverCore(self)

@cached_property
def peripherals(self):
all_peripherals = []
for s in self.gpio.signals_all:
d = copy_keys(s, "driver", "instance")
if len(d): all_peripherals.append(d);

# Signals are not enough, since there are peripherals that don't have signals.
# Example: STM32F401RE < 64pins: SPI4 cannot be connected to any pins.
for d in self._properties["driver"]:
driver = d["name"]
if driver in ["gpio", "core"]:
continue
elif "instance" in d:
all_peripherals.extend( {"driver": driver, "instance": int(i["name"])} for i in d["instance"] )
else:
all_peripherals.append( {"driver": driver} )

for r in self.gpio._driver.get("remap", {}):
d = copy_keys(r, "driver", "instance")
if len(d): all_peripherals.append(d);

return all_peripherals
Loading

0 comments on commit a7f5782

Please sign in to comment.