This is a direct search-and-replace copy of the PWM code. The name is chosen to match Linux's iio-bindings.txt. Signed-off-by: Jim Paris <jim@jtan.com>
1712 lines
56 KiB
Python
1712 lines
56 KiB
Python
# Copyright (c) 2019 Nordic Semiconductor ASA
|
|
# Copyright (c) 2019 Linaro Limited
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
# Tip: You can view just the documentation with 'pydoc3 edtlib'
|
|
|
|
"""
|
|
Library for working with .dts files and bindings at a higher level compared to
|
|
dtlib. Deals with things at the level of devices, registers, interrupts,
|
|
compatibles, bindings, etc., as opposed to dtlib, which is just a low-level
|
|
device tree parser.
|
|
|
|
Each device tree node (dtlib.Node) gets a Device instance, which has all the
|
|
information related to the device, derived from both the device tree and from
|
|
the binding for the device.
|
|
|
|
Bindings are files that describe device tree nodes. Device tree nodes are
|
|
usually mapped to bindings via their 'compatible = "..."' property, but binding
|
|
data can also come from a 'sub-node:' key in the binding for the parent device
|
|
tree node.
|
|
|
|
The top-level entry point of the library is the EDT class. EDT.__init__() takes
|
|
a .dts file to parse and the path of a directory containing bindings.
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
import yaml
|
|
|
|
from dtlib import DT, DTError, to_num, to_nums
|
|
|
|
# NOTE: testedtlib.py is the test suite for this library. It can be run
|
|
# directly.
|
|
|
|
# Implementation notes
|
|
# --------------------
|
|
#
|
|
# A '_' prefix on an identifier in Python is a convention for marking it private.
|
|
# Please do not access private things. Instead, think of what API you need, and
|
|
# add it.
|
|
#
|
|
# This library is layered on top of dtlib, and is not meant to expose it to
|
|
# clients. This keeps the header generation script simple.
|
|
#
|
|
# General biased advice:
|
|
#
|
|
# - Consider using @property for APIs that don't need parameters. It makes
|
|
# functions look like attributes, which is less awkward in clients, and makes
|
|
# it easy to switch back and forth between variables and functions.
|
|
#
|
|
# - Think about the data type of the thing you're exposing. Exposing something
|
|
# as e.g. a list or a dictionary is often nicer and more flexible than adding
|
|
# a function.
|
|
#
|
|
# - Avoid get_*() prefixes on functions. Name them after the thing they return
|
|
# instead. This often makes the code read more naturally in callers.
|
|
#
|
|
# Also, consider using @property instead of get_*().
|
|
#
|
|
# - Don't expose dtlib stuff directly.
|
|
#
|
|
# - Add documentation for any new APIs you add.
|
|
#
|
|
# The convention here is that docstrings (quoted strings) are used for public
|
|
# APIs, and "doc comments" for internal functions.
|
|
#
|
|
# @properties are documented in the class docstring, as if they were
|
|
# variables. See the existing @properties for a template.
|
|
#
|
|
# - Please use ""-quoted strings instead of ''-quoted strings, just to make
|
|
# things consistent (''-quoting is more common otherwise in Python)
|
|
|
|
#
|
|
# Public classes
|
|
#
|
|
|
|
|
|
class EDT:
|
|
"""
|
|
Represents a "high-level" view of a device tree, with a list of devices
|
|
that each have some number of registers, etc.
|
|
|
|
These attributes are available on EDT objects:
|
|
|
|
devices:
|
|
A list of Device objects for the devices
|
|
|
|
dts_path:
|
|
The .dts path passed to __init__()
|
|
|
|
bindings_dirs:
|
|
The bindings directory paths passed to __init__()
|
|
"""
|
|
def __init__(self, dts, bindings_dirs):
|
|
"""
|
|
EDT constructor. This is the top-level entry point to the library.
|
|
|
|
dts:
|
|
Path to device tree .dts file
|
|
|
|
bindings_dirs:
|
|
List of paths to directories containing bindings, in YAML format.
|
|
These directories are recursively searched for .yaml files.
|
|
"""
|
|
self.dts_path = dts
|
|
self.bindings_dirs = bindings_dirs
|
|
|
|
self._dt = DT(dts)
|
|
_check_dt(self._dt)
|
|
|
|
self._init_compat2binding(bindings_dirs)
|
|
self._init_devices()
|
|
|
|
def get_dev(self, path):
|
|
"""
|
|
Returns the Device at the DT path or alias 'path'. Raises EDTError if
|
|
the path or alias doesn't exist.
|
|
"""
|
|
try:
|
|
return self._node2dev[self._dt.get_node(path)]
|
|
except DTError as e:
|
|
_err(e)
|
|
|
|
def chosen_dev(self, name):
|
|
"""
|
|
Returns the Device pointed at by the property named 'name' in /chosen,
|
|
or None if the property is missing
|
|
"""
|
|
try:
|
|
chosen = self._dt.get_node("/chosen")
|
|
except DTError:
|
|
# No /chosen node
|
|
return None
|
|
|
|
if name not in chosen.props:
|
|
return None
|
|
|
|
path = chosen.props[name].to_string()
|
|
try:
|
|
return self._node2dev[self._dt.get_node(path)]
|
|
except DTError:
|
|
_err("{} in /chosen points to {}, which does not exist"
|
|
.format(name, path))
|
|
|
|
def _init_compat2binding(self, bindings_dirs):
|
|
# Creates self._compat2binding. This is a dictionary that maps
|
|
# (<compatible>, <bus>) tuples (both strings) to (<binding>, <path>)
|
|
# tuples. <binding> is the binding in parsed PyYAML format, and <path>
|
|
# the path to the binding (nice for binding-related error messages).
|
|
#
|
|
# For example, self._compat2binding["company,dev", "can"] contains the
|
|
# binding/path for the 'company,dev' device, when it appears on the CAN
|
|
# bus.
|
|
#
|
|
# For bindings that don't specify a bus, <bus> is None, so that e.g.
|
|
# self._compat2binding["company,notonbus", None] contains the binding.
|
|
#
|
|
# Only bindings for 'compatible' strings that appear in the device tree
|
|
# are loaded.
|
|
|
|
dt_compats = _dt_compats(self._dt)
|
|
self._binding_paths = _binding_paths(bindings_dirs)
|
|
|
|
# Add '!include foo.yaml' handling.
|
|
#
|
|
# Do yaml.Loader.add_constructor() instead of yaml.add_constructor() to be
|
|
# compatible with both version 3.13 and version 5.1 of PyYAML.
|
|
#
|
|
# TODO: Is there some 3.13/5.1-compatible way to only do this once, even
|
|
# if multiple EDT objects are created?
|
|
yaml.Loader.add_constructor("!include", self._binding_include)
|
|
|
|
self._compat2binding = {}
|
|
for binding_path in self._binding_paths:
|
|
compat = _binding_compat(binding_path)
|
|
if compat in dt_compats:
|
|
binding = _load_binding(binding_path)
|
|
self._compat2binding[compat, _binding_bus(binding)] = \
|
|
(binding, binding_path)
|
|
|
|
def _binding_include(self, loader, node):
|
|
# Implements !include. Returns a list with the YAML structures for the
|
|
# included files (a single-element list if the !include is for a single
|
|
# file).
|
|
|
|
if isinstance(node, yaml.ScalarNode):
|
|
# !include foo.yaml
|
|
return [self._binding_include_file(loader.construct_scalar(node))]
|
|
|
|
if isinstance(node, yaml.SequenceNode):
|
|
# !include [foo.yaml, bar.yaml]
|
|
return [self._binding_include_file(filename)
|
|
for filename in loader.construct_sequence(node)]
|
|
|
|
_binding_inc_error("unrecognised node type in !include statement")
|
|
|
|
def _binding_include_file(self, filename):
|
|
# _binding_include() helper for loading an !include'd file. !include
|
|
# takes just the basename of the file, so we need to make sure there
|
|
# aren't multiple candidates.
|
|
|
|
paths = [path for path in self._binding_paths
|
|
if os.path.basename(path) == filename]
|
|
|
|
if not paths:
|
|
_binding_inc_error("'{}' not found".format(filename))
|
|
|
|
if len(paths) > 1:
|
|
_binding_inc_error("multiple candidates for '{}' in !include: {}"
|
|
.format(filename, ", ".join(paths)))
|
|
|
|
with open(paths[0], encoding="utf-8") as f:
|
|
return yaml.load(f, Loader=yaml.Loader)
|
|
|
|
def _init_devices(self):
|
|
# Creates a list of devices (Device objects) from the DT nodes, in
|
|
# self.devices. 'dt' is the dtlib.DT instance for the device tree.
|
|
|
|
# Maps dtlib.Node's to their corresponding Devices
|
|
self._node2dev = {}
|
|
|
|
self.devices = []
|
|
|
|
for node in self._dt.node_iter():
|
|
# Warning: Device.__init__() relies on parent Devices being created
|
|
# before their children. This is guaranteed by node_iter().
|
|
dev = Device(self, node)
|
|
self.devices.append(dev)
|
|
self._node2dev[node] = dev
|
|
|
|
for dev in self.devices:
|
|
# These depend on all Device objects having been created, so we do
|
|
# them separately
|
|
dev._init_interrupts()
|
|
dev._init_gpios()
|
|
dev._init_pwms()
|
|
dev._init_iochannels()
|
|
dev._init_clocks()
|
|
|
|
def __repr__(self):
|
|
return "<EDT for '{}', binding directories '{}'>".format(
|
|
self.dts_path, self.bindings_dirs)
|
|
|
|
|
|
class Device:
|
|
"""
|
|
Represents a device. There's a one-to-one correspondence between device
|
|
tree nodes and Devices.
|
|
|
|
These attributes are available on Device objects:
|
|
|
|
edt:
|
|
The EDT instance this device is from
|
|
|
|
name:
|
|
The name of the device. This is fetched from the node name.
|
|
|
|
unit_addr:
|
|
An integer with the ...@<unit-address> portion of the node name,
|
|
translated through any 'ranges' properties on parent nodes, or None if
|
|
the node name has no unit-address portion
|
|
|
|
path:
|
|
The device tree path of the device
|
|
|
|
label:
|
|
The text from the 'label' property on the DT node of the Device, or None
|
|
if the node has no 'label'
|
|
|
|
parent:
|
|
The parent Device, or None if there is no parent
|
|
|
|
enabled:
|
|
True unless the device's node has 'status = "disabled"'
|
|
|
|
read_only:
|
|
True if the DT node of the Device has a 'read-only' property, and False
|
|
otherwise
|
|
|
|
instance_no:
|
|
Dictionary that maps each 'compatible' string for the device to a unique
|
|
index among all devices that have that 'compatible' string.
|
|
|
|
As an example, 'instance_no["foo,led"] == 3' can be read as "this is the
|
|
fourth foo,led device".
|
|
|
|
Only enabled devices (status != "disabled") are counted. 'instance_no' is
|
|
meaningless for disabled devices.
|
|
|
|
matching_compat:
|
|
The 'compatible' string for the binding that matched the device, or
|
|
None if the device has no binding
|
|
|
|
description:
|
|
The description string from the binding file for the device, or None if
|
|
the device has no binding. Trailing whitespace (including newlines) is
|
|
removed.
|
|
|
|
binding_path:
|
|
The path to the binding file for the device, or None if the device has no
|
|
binding
|
|
|
|
compats:
|
|
A list of 'compatible' strings for the device, in the same order that
|
|
they're listed in the .dts file
|
|
|
|
regs:
|
|
A list of Register objects for the device's registers
|
|
|
|
props:
|
|
A dictionary that maps property names to Property objects. Property
|
|
objects are created for all DT properties on the device that are
|
|
mentioned in 'properties:' in the binding.
|
|
|
|
aliases:
|
|
A list of aliases for the device. This is fetched from the /aliases node.
|
|
|
|
interrupts:
|
|
A list of Interrupt objects for the interrupts generated by the device
|
|
|
|
gpios:
|
|
A dictionary that maps the <prefix> part in '<prefix>-gpios' properties
|
|
to a list of GPIO objects (see the GPIO class).
|
|
|
|
For example, 'foo-gpios = <&gpio1 1 2 &gpio2 3 4>' makes gpios["foo"] a
|
|
list of two GPIO objects.
|
|
|
|
pwms:
|
|
A list of PWM objects, derived from the 'pwms' property. The list is
|
|
empty if the device has no 'pwms' property.
|
|
|
|
iochannels:
|
|
A list of IOChannel objects, derived from the 'io-channels'
|
|
property. The list is empty if the device has no 'io-channels'
|
|
property.
|
|
|
|
clocks:
|
|
A list of Clock objects, derived from the 'clocks' property. The list is
|
|
empty if the device has no 'clocks' property.
|
|
|
|
bus:
|
|
The bus for the device as specified in its binding, e.g. "i2c" or "spi".
|
|
None if the binding doesn't specify a bus.
|
|
|
|
flash_controller:
|
|
The flash controller for the device. Only meaningful for devices
|
|
representing flash partitions.
|
|
"""
|
|
@property
|
|
def name(self):
|
|
"See the class docstring"
|
|
return self._node.name
|
|
|
|
@property
|
|
def unit_addr(self):
|
|
"See the class docstring"
|
|
|
|
# TODO: Return a plain string here later, like dtlib.Node.unit_addr?
|
|
|
|
if "@" not in self.name:
|
|
return None
|
|
|
|
try:
|
|
addr = int(self.name.split("@", 1)[1], 16)
|
|
except ValueError:
|
|
_err("{!r} has non-hex unit address".format(self))
|
|
|
|
addr = _translate(addr, self._node)
|
|
|
|
if self.regs and self.regs[0].addr != addr:
|
|
_warn("unit-address and first reg (0x{:x}) don't match for {}"
|
|
.format(self.regs[0].addr, self.name))
|
|
|
|
return addr
|
|
|
|
@property
|
|
def path(self):
|
|
"See the class docstring"
|
|
return self._node.path
|
|
|
|
@property
|
|
def label(self):
|
|
"See the class docstring"
|
|
if "label" in self._node.props:
|
|
return self._node.props["label"].to_string()
|
|
return None
|
|
|
|
@property
|
|
def parent(self):
|
|
"See the class docstring"
|
|
return self.edt._node2dev.get(self._node.parent)
|
|
|
|
@property
|
|
def enabled(self):
|
|
"See the class docstring"
|
|
return "status" not in self._node.props or \
|
|
self._node.props["status"].to_string() != "disabled"
|
|
|
|
@property
|
|
def read_only(self):
|
|
"See the class docstring"
|
|
return "read-only" in self._node.props
|
|
|
|
@property
|
|
def aliases(self):
|
|
"See the class docstring"
|
|
return [alias for alias, node in self._node.dt.alias2node.items()
|
|
if node is self._node]
|
|
|
|
@property
|
|
def bus(self):
|
|
"See the class docstring"
|
|
return _binding_bus(self._binding)
|
|
|
|
@property
|
|
def flash_controller(self):
|
|
"See the class docstring"
|
|
|
|
# The node path might be something like
|
|
# /flash-controller@4001E000/flash@0/partitions/partition@fc000. We go
|
|
# up two levels to get the flash and check its compat. The flash
|
|
# controller might be the flash itself (for cases like NOR flashes).
|
|
# For the case of 'soc-nv-flash', we assume the controller is the
|
|
# parent of the flash node.
|
|
|
|
if not self.parent or not self.parent.parent:
|
|
_err("flash partition {!r} lacks parent or grandparent node"
|
|
.format(self))
|
|
|
|
controller = self.parent.parent
|
|
if controller.matching_compat == "soc-nv-flash":
|
|
return controller.parent
|
|
return controller
|
|
|
|
def __repr__(self):
|
|
return "<Device {} in '{}', {}>".format(
|
|
self.path, self.edt.dts_path,
|
|
"binding " + self.binding_path if self.binding_path
|
|
else "no binding")
|
|
|
|
def __init__(self, edt, node):
|
|
"Private constructor. Not meant to be called by clients."
|
|
|
|
# Interrupts, GPIOs, PWMs, io-channels, and clocks are
|
|
# initialized separately, because they depend on all Devices
|
|
# existing
|
|
|
|
self.edt = edt
|
|
self._node = node
|
|
|
|
self._init_binding()
|
|
self._init_props()
|
|
self._init_regs()
|
|
self._set_instance_no()
|
|
|
|
def _init_binding(self):
|
|
# Initializes Device.matching_compat, Device._binding, and
|
|
# Device.binding_path.
|
|
#
|
|
# Device._binding holds the data from the device's binding file, in the
|
|
# format returned by PyYAML (plain Python lists, dicts, etc.), or None
|
|
# if the device has no binding.
|
|
|
|
# This relies on the parent of the Device having already been
|
|
# initialized, which is guaranteed by going through the nodes in
|
|
# node_iter() order.
|
|
|
|
if "compatible" in self._node.props:
|
|
self.compats = self._node.props["compatible"].to_strings()
|
|
bus = self._bus_from_parent_binding()
|
|
|
|
for compat in self.compats:
|
|
if (compat, bus) in self.edt._compat2binding:
|
|
# Binding found
|
|
self.matching_compat = compat
|
|
self._binding, self.binding_path = \
|
|
self.edt._compat2binding[compat, bus]
|
|
|
|
self.description = self._binding.get("description")
|
|
if self.description:
|
|
self.description = self.description.rstrip()
|
|
|
|
return
|
|
else:
|
|
# No 'compatible' property. See if the parent has a 'sub-node:' key
|
|
# that gives the binding.
|
|
|
|
self.compats = []
|
|
|
|
if self.parent and self.parent._binding and \
|
|
"sub-node" in self.parent._binding:
|
|
|
|
# Binding found
|
|
self._binding = self.parent._binding["sub-node"]
|
|
self.binding_path = self.parent.binding_path
|
|
|
|
self.description = self.parent._binding.get("description")
|
|
if self.description:
|
|
self.description = self.description.rstrip()
|
|
|
|
self.matching_compat = self.parent.matching_compat
|
|
return
|
|
|
|
# No binding found
|
|
self.matching_compat = self._binding = self.binding_path = \
|
|
self.description = None
|
|
|
|
def _bus_from_parent_binding(self):
|
|
# _init_binding() helper. Returns the bus specified by
|
|
# 'child: bus: ...' in the parent binding, or None if missing.
|
|
|
|
if not self.parent:
|
|
return None
|
|
|
|
binding = self.parent._binding
|
|
if binding and "child" in binding:
|
|
return binding["child"].get("bus")
|
|
return None
|
|
|
|
def _init_props(self):
|
|
# Creates self.props. See the class docstring.
|
|
|
|
self.props = {}
|
|
|
|
if not self._binding or "properties" not in self._binding:
|
|
return
|
|
|
|
for name, options in self._binding["properties"].items():
|
|
self._init_prop(name, options)
|
|
|
|
def _init_prop(self, name, options):
|
|
# _init_props() helper for initializing a single property
|
|
|
|
prop_type = options.get("type")
|
|
if not prop_type:
|
|
_err("'{}' in {} lacks 'type'".format(name, self.binding_path))
|
|
|
|
# "Dummy" type for properties like '...-gpios', so that we can require
|
|
# all entries in 'properties:' to have a 'type: ...'. It might make
|
|
# sense to have gpios in 'properties:' for other reasons, e.g. to set
|
|
# 'category: required'.
|
|
if prop_type == "compound":
|
|
return
|
|
|
|
val = self._prop_val(name, prop_type,
|
|
options.get("category") == "optional")
|
|
if val is None:
|
|
# 'category: optional' property that wasn't there
|
|
return
|
|
|
|
# Skip properties that start with '#', like '#size-cells', and mapping
|
|
# properties like 'gpio-map'/'interrupt-map'
|
|
if name[0] == "#" or name.endswith("-map"):
|
|
return
|
|
|
|
prop = Property()
|
|
prop.dev = self
|
|
prop.name = name
|
|
prop.description = options.get("description")
|
|
if prop.description:
|
|
prop.description = prop.description.rstrip()
|
|
prop.val = val
|
|
enum = options.get("enum")
|
|
if enum is None:
|
|
prop.enum_index = None
|
|
else:
|
|
if val not in enum:
|
|
_err("value ({}) for property ({}) is not in enumerated "
|
|
"list {} for node {}".format(val, name, enum, self.name))
|
|
|
|
prop.enum_index = enum.index(val)
|
|
|
|
self.props[name] = prop
|
|
|
|
def _prop_val(self, name, prop_type, optional):
|
|
# _init_prop() helper for getting the property's value
|
|
|
|
node = self._node
|
|
|
|
if prop_type == "boolean":
|
|
# True/False
|
|
return name in node.props
|
|
|
|
prop = node.props.get(name)
|
|
if not prop:
|
|
if not optional and self.enabled:
|
|
_err("'{}' is marked as required in 'properties:' in {}, but "
|
|
"does not appear in {!r}".format(
|
|
name, self.binding_path, node))
|
|
|
|
return None
|
|
|
|
if prop_type == "int":
|
|
return prop.to_num()
|
|
|
|
if prop_type == "array":
|
|
return prop.to_nums()
|
|
|
|
if prop_type == "uint8-array":
|
|
return prop.value # Plain 'bytes'
|
|
|
|
if prop_type == "string":
|
|
return prop.to_string()
|
|
|
|
if prop_type == "string-array":
|
|
return prop.to_strings()
|
|
|
|
_err("'{}' in 'properties:' in {} has unknown type '{}'"
|
|
.format(name, self.binding_path, prop_type))
|
|
|
|
def _init_regs(self):
|
|
# Initializes self.regs
|
|
|
|
node = self._node
|
|
|
|
self.regs = []
|
|
|
|
if "reg" not in node.props:
|
|
return
|
|
|
|
address_cells = _address_cells(node)
|
|
size_cells = _size_cells(node)
|
|
|
|
for raw_reg in _slice(node, "reg", 4*(address_cells + size_cells)):
|
|
reg = Register()
|
|
reg.dev = self
|
|
reg.addr = _translate(to_num(raw_reg[:4*address_cells]), node)
|
|
reg.size = to_num(raw_reg[4*address_cells:])
|
|
if size_cells != 0 and reg.size == 0:
|
|
_err("zero-sized 'reg' in {!r} seems meaningless (maybe you "
|
|
"want a size of one or #size-cells = 0 instead)"
|
|
.format(self._node))
|
|
|
|
self.regs.append(reg)
|
|
|
|
_add_names(node, "reg", self.regs)
|
|
|
|
def _init_interrupts(self):
|
|
# Initializes self.interrupts
|
|
|
|
node = self._node
|
|
|
|
self.interrupts = []
|
|
|
|
for controller_node, spec in _interrupts(node):
|
|
interrupt = Interrupt()
|
|
interrupt.dev = self
|
|
interrupt.controller = self.edt._node2dev[controller_node]
|
|
interrupt.specifier = self._named_cells(interrupt.controller, spec,
|
|
"interrupt")
|
|
|
|
self.interrupts.append(interrupt)
|
|
|
|
_add_names(node, "interrupt", self.interrupts)
|
|
|
|
def _init_gpios(self):
|
|
# Initializes self.gpios
|
|
|
|
self.gpios = {}
|
|
|
|
for prefix, gpios in _gpios(self._node).items():
|
|
self.gpios[prefix] = []
|
|
for controller_node, spec in gpios:
|
|
gpio = GPIO()
|
|
gpio.dev = self
|
|
gpio.controller = self.edt._node2dev[controller_node]
|
|
gpio.specifier = self._named_cells(gpio.controller, spec,
|
|
"GPIO")
|
|
gpio.name = prefix
|
|
|
|
self.gpios[prefix].append(gpio)
|
|
|
|
def _init_clocks(self):
|
|
# Initializes self.clocks
|
|
|
|
self.clocks = self._simple_phandle_val_list("clock", Clock)
|
|
|
|
# Initialize Clock.frequency
|
|
for clock in self.clocks:
|
|
controller = clock.controller
|
|
if "fixed-clock" in controller.compats:
|
|
if "clock-frequency" not in controller.props:
|
|
_err("{!r} is a 'fixed-clock', but either lacks a "
|
|
"'clock-frequency' property or does not have "
|
|
"it specified in its binding".format(controller))
|
|
|
|
clock.frequency = controller.props["clock-frequency"].val
|
|
else:
|
|
clock.frequency = None
|
|
|
|
def _init_pwms(self):
|
|
# Initializes self.pwms
|
|
|
|
self.pwms = self._simple_phandle_val_list("pwm", PWM)
|
|
|
|
def _init_iochannels(self):
|
|
# Initializes self.iochannels
|
|
|
|
self.iochannels = self._simple_phandle_val_list("io-channel", IOChannel)
|
|
|
|
def _simple_phandle_val_list(self, name, cls):
|
|
# Helper for parsing properties like
|
|
#
|
|
# <name>s = <phandle value phandle value ...>
|
|
# (e.g., gpios = <&foo 1 2 &bar 3 4>)
|
|
#
|
|
# , where each phandle points to a node that has a
|
|
#
|
|
# #<name>-cells = <size>
|
|
#
|
|
# property that gives the number of cells in the value after the
|
|
# phandle. Also parses any
|
|
#
|
|
# <name>-names = "...", "...", ...
|
|
#
|
|
# Arguments:
|
|
#
|
|
# name:
|
|
# The <name>, e.g. "clock" or "pwm". Note: no -s in the argument.
|
|
#
|
|
# cls:
|
|
# A class object. Instances of this class will be created and the
|
|
# 'dev', 'controller', 'specifier', and 'name' fields initialized.
|
|
# See the documentation for e.g. the PWM class.
|
|
#
|
|
# Returns a list of 'cls' instances.
|
|
|
|
prop = self._node.props.get(name + "s")
|
|
if not prop:
|
|
return []
|
|
|
|
res = []
|
|
|
|
for controller_node, spec in _phandle_val_list(prop, name):
|
|
obj = cls()
|
|
obj.dev = self
|
|
obj.controller = self.edt._node2dev[controller_node]
|
|
obj.specifier = self._named_cells(obj.controller, spec, name)
|
|
|
|
res.append(obj)
|
|
|
|
_add_names(self._node, name, res)
|
|
|
|
return res
|
|
|
|
def _named_cells(self, controller, spec, controller_s):
|
|
# _init_{interrupts,gpios}() helper. Returns a dictionary that maps
|
|
# #cell names given in the binding for 'controller' to cell values.
|
|
# 'spec' is the raw interrupt/GPIO data, and 'controller_s' a string
|
|
# that gives the context (for error messages).
|
|
|
|
if not controller._binding:
|
|
_err("{} controller {!r} for {!r} lacks binding"
|
|
.format(controller_s, controller._node, self._node))
|
|
|
|
if "#cells" in controller._binding:
|
|
cell_names = controller._binding["#cells"]
|
|
if not isinstance(cell_names, list):
|
|
_err("binding for {} controller {!r} has malformed #cells array"
|
|
.format(controller_s, controller._node))
|
|
else:
|
|
# Treat no #cells in the binding the same as an empty #cells, so
|
|
# that bindings don't have to have an empty #cells for e.g.
|
|
# '#clock-cells = <0>'.
|
|
cell_names = []
|
|
|
|
spec_list = to_nums(spec)
|
|
if len(spec_list) != len(cell_names):
|
|
_err("unexpected #cells length in binding for {!r} - {} instead "
|
|
"of {}".format(controller._node, len(cell_names),
|
|
len(spec_list)))
|
|
|
|
return dict(zip(cell_names, spec_list))
|
|
|
|
def _set_instance_no(self):
|
|
# Initializes self.instance_no
|
|
|
|
self.instance_no = {}
|
|
|
|
for compat in self.compats:
|
|
self.instance_no[compat] = 0
|
|
for other_dev in self.edt.devices:
|
|
if compat in other_dev.compats and other_dev.enabled:
|
|
self.instance_no[compat] += 1
|
|
|
|
|
|
class Register:
|
|
"""
|
|
Represents a register on a device.
|
|
|
|
These attributes are available on Register objects:
|
|
|
|
dev:
|
|
The Device instance this register is from
|
|
|
|
name:
|
|
The name of the register as given in the 'reg-names' property, or None if
|
|
there is no 'reg-names' property
|
|
|
|
addr:
|
|
The starting address of the register, in the parent address space. Any
|
|
'ranges' properties are taken into account.
|
|
|
|
size:
|
|
The length of the register in bytes
|
|
"""
|
|
def __repr__(self):
|
|
fields = []
|
|
|
|
if self.name is not None:
|
|
fields.append("name: " + self.name)
|
|
fields.append("addr: " + hex(self.addr))
|
|
fields.append("size: " + hex(self.size))
|
|
|
|
return "<Register, {}>".format(", ".join(fields))
|
|
|
|
|
|
class Interrupt:
|
|
"""
|
|
Represents an interrupt generated by a device.
|
|
|
|
These attributes are available on Interrupt objects:
|
|
|
|
dev:
|
|
The Device instance that generated the interrupt
|
|
|
|
name:
|
|
The name of the interrupt as given in the 'interrupt-names' property, or
|
|
None if there is no 'interrupt-names' property
|
|
|
|
controller:
|
|
The Device instance for the controller the interrupt gets sent to. Any
|
|
'interrupt-map' is taken into account, so that this is the final
|
|
controller node.
|
|
|
|
specifier:
|
|
A dictionary that maps names from the #cells portion of the binding to
|
|
cell values in the interrupt specifier. 'interrupts = <1 2>' might give
|
|
{"irq": 1, "level": 2}, for example.
|
|
"""
|
|
def __repr__(self):
|
|
fields = []
|
|
|
|
if self.name is not None:
|
|
fields.append("name: " + self.name)
|
|
|
|
fields.append("target: {}".format(self.controller))
|
|
fields.append("specifier: {}".format(self.specifier))
|
|
|
|
return "<Interrupt, {}>".format(", ".join(fields))
|
|
|
|
|
|
class GPIO:
|
|
"""
|
|
Represents a GPIO used by a device.
|
|
|
|
These attributes are available on GPIO objects:
|
|
|
|
dev:
|
|
The Device instance that uses the GPIO
|
|
|
|
name:
|
|
The name of the gpio as extracted out of the "<NAME>-gpios" property. If
|
|
the property is just "gpios" than there is no name.
|
|
|
|
controller:
|
|
The Device instance for the controller of the GPIO
|
|
|
|
specifier:
|
|
A dictionary that maps names from the #cells portion of the binding to
|
|
cell values in the gpio specifier. 'foo-gpios = <&gpioc 5 0>' might give
|
|
{"pin": 5, "flags": 0}, for example.
|
|
"""
|
|
def __repr__(self):
|
|
fields = []
|
|
|
|
if self.name is not None:
|
|
fields.append("name: " + self.name)
|
|
|
|
fields.append("target: {}".format(self.controller))
|
|
fields.append("specifier: {}".format(self.specifier))
|
|
|
|
return "<GPIO, {}>".format(", ".join(fields))
|
|
|
|
|
|
class Clock:
|
|
"""
|
|
Represents a clock used by a device.
|
|
|
|
These attributes are available on Clock objects:
|
|
|
|
dev:
|
|
The Device instance that uses the clock
|
|
|
|
name:
|
|
The name of the clock as given in the 'clock-names' property, or
|
|
None if there is no 'clock-names' property
|
|
|
|
controller:
|
|
The Device instance for the controller of the clock.
|
|
|
|
frequency:
|
|
The frequency of the clock for fixed clocks ('fixed-clock' in
|
|
'compatible'), as an integer. Derived from the 'clock-frequency'
|
|
property. None if the clock is not a fixed clock.
|
|
|
|
specifier:
|
|
A dictionary that maps names from the #cells portion of the binding to
|
|
cell values in the clock specifier
|
|
"""
|
|
def __repr__(self):
|
|
fields = []
|
|
|
|
if self.name is not None:
|
|
fields.append("name: " + self.name)
|
|
|
|
if self.frequency is not None:
|
|
fields.append("frequency: {}".format(self.frequency))
|
|
|
|
fields.append("target: {}".format(self.controller))
|
|
fields.append("specifier: {}".format(self.specifier))
|
|
|
|
return "<Clock, {}>".format(", ".join(fields))
|
|
|
|
|
|
class PWM:
|
|
"""
|
|
Represents a PWM used by a device.
|
|
|
|
These attributes are available on PWM objects:
|
|
|
|
dev:
|
|
The Device instance that uses the PWM
|
|
|
|
name:
|
|
The name of the pwm as given in the 'pwm-names' property, or the
|
|
node name if the 'pwm-names' property doesn't exist.
|
|
|
|
controller:
|
|
The Device instance for the controller of the PWM
|
|
|
|
specifier:
|
|
A dictionary that maps names from the #cells portion of the binding to
|
|
cell values in the pwm specifier. 'pwms = <&pwm 0 5000000>' might give
|
|
{"channel": 0, "period": 5000000}, for example.
|
|
"""
|
|
def __repr__(self):
|
|
fields = []
|
|
|
|
if self.name is not None:
|
|
fields.append("name: " + self.name)
|
|
|
|
fields.append("target: {}".format(self.controller))
|
|
fields.append("specifier: {}".format(self.specifier))
|
|
|
|
return "<PWM, {}>".format(", ".join(fields))
|
|
|
|
|
|
class IOChannel:
|
|
"""
|
|
Represents an IO channel used by a device, similar to the property used
|
|
by the Linux IIO bindings and described at:
|
|
https://www.kernel.org/doc/Documentation/devicetree/bindings/iio/iio-bindings.txt
|
|
|
|
These attributes are available on IO channel objects:
|
|
|
|
dev:
|
|
The Device instance that uses the IO channel
|
|
|
|
name:
|
|
The name of the IO channel as given in the 'io-channel-names' property,
|
|
or the node name if the 'io-channel-names' property doesn't exist.
|
|
|
|
controller:
|
|
The Device instance for the controller of the IO channel
|
|
|
|
specifier:
|
|
A dictionary that maps names from the #cells portion of the binding to
|
|
cell values in the io-channel specifier. 'io-channels = <&adc 3>' might
|
|
give {"input": 3}, for example.
|
|
"""
|
|
def __repr__(self):
|
|
fields = []
|
|
|
|
if self.name is not None:
|
|
fields.append("name: " + self.name)
|
|
|
|
fields.append("target: {}".format(self.controller))
|
|
fields.append("specifier: {}".format(self.specifier))
|
|
|
|
return "<IOChannel, {}>".format(", ".join(fields))
|
|
|
|
|
|
class Property:
|
|
"""
|
|
Represents a property on a Device, as set in its DT node and with
|
|
additional info from the 'properties:' section of the binding.
|
|
|
|
Only properties mentioned in 'properties:' get created.
|
|
|
|
These attributes are available on Property objects:
|
|
|
|
dev:
|
|
The Device instance the property is on
|
|
|
|
name:
|
|
The name of the property
|
|
|
|
description:
|
|
The description string from the property as given in the binding, or None
|
|
if missing. Trailing whitespace (including newlines) is removed.
|
|
|
|
val:
|
|
The value of the property, with the format determined by the 'type:'
|
|
key from the binding
|
|
|
|
enum_index:
|
|
The index of the property's value in the 'enum:' list in the binding, or
|
|
None if the binding has no 'enum:'
|
|
"""
|
|
def __repr__(self):
|
|
fields = ["name: " + self.name,
|
|
# repr() to deal with lists
|
|
"value: " + repr(self.val)]
|
|
|
|
if self.enum_index is not None:
|
|
fields.append("enum index: {}".format(self.enum_index))
|
|
|
|
return "<Property, {}>".format(", ".join(fields))
|
|
|
|
|
|
class EDTError(Exception):
|
|
"Exception raised for Extended Device Tree-related errors"
|
|
|
|
|
|
#
|
|
# Public global functions
|
|
#
|
|
|
|
|
|
def spi_dev_cs_gpio(dev):
|
|
# Returns an SPI device's GPIO chip select if it exists, as a GPIO
|
|
# instance, and None otherwise. See
|
|
# Documentation/devicetree/bindings/spi/spi-bus.txt in the Linux kernel.
|
|
|
|
if dev.bus == "spi" and dev.parent:
|
|
parent_cs = dev.parent.gpios.get("cs")
|
|
if parent_cs:
|
|
# cs-gpios is indexed by the unit address
|
|
cs_index = dev.regs[0].addr
|
|
if cs_index >= len(parent_cs):
|
|
_err("index from 'regs' in {!r} ({}) is >= number of cs-gpios "
|
|
"in {!r} ({})".format(
|
|
dev, cs_index, dev.parent, len(parent_cs)))
|
|
|
|
return parent_cs[cs_index]
|
|
|
|
return None
|
|
|
|
|
|
#
|
|
# Private global functions
|
|
#
|
|
|
|
|
|
def _dt_compats(dt):
|
|
# Returns a set() with all 'compatible' strings in the device tree
|
|
# represented by dt (a dtlib.DT instance)
|
|
|
|
return {compat
|
|
for node in dt.node_iter()
|
|
if "compatible" in node.props
|
|
for compat in node.props["compatible"].to_strings()}
|
|
|
|
|
|
def _binding_paths(bindings_dirs):
|
|
# Returns a list with the paths to all bindings (.yaml files) in
|
|
# 'bindings_dirs'
|
|
|
|
binding_paths = []
|
|
|
|
for bindings_dir in bindings_dirs:
|
|
for root, _, filenames in os.walk(bindings_dir):
|
|
for filename in filenames:
|
|
if filename.endswith(".yaml"):
|
|
binding_paths.append(os.path.join(root, filename))
|
|
|
|
return binding_paths
|
|
|
|
|
|
def _binding_compat(binding_path):
|
|
# Returns the compatible string specified in the binding at 'binding_path'.
|
|
# Uses a regex to avoid having to parse the bindings, which is slow when
|
|
# done for all bindings.
|
|
|
|
with open(binding_path, encoding="utf-8") as binding:
|
|
for line in binding:
|
|
match = re.match(r'\s+constraint:\s*"([^"]*)"', line)
|
|
if match:
|
|
return match.group(1)
|
|
|
|
return None
|
|
|
|
|
|
def _binding_bus(binding):
|
|
# Returns the bus specified in 'binding' (the bus the device described by
|
|
# 'binding' is on), e.g. "i2c", or None if 'binding' is None or doesn't
|
|
# specify a bus
|
|
|
|
if binding and "parent" in binding:
|
|
return binding["parent"].get("bus")
|
|
return None
|
|
|
|
|
|
def _binding_inc_error(msg):
|
|
# Helper for reporting errors in the !include implementation
|
|
|
|
raise yaml.constructor.ConstructorError(None, None, "error: " + msg)
|
|
|
|
|
|
def _load_binding(path):
|
|
# Loads a top-level binding .yaml file from 'path', also handling any
|
|
# !include'd files. Returns the parsed PyYAML output (Python
|
|
# lists/dictionaries/strings/etc. representing the file).
|
|
|
|
with open(path, encoding="utf-8") as f:
|
|
return _merge_included_bindings(yaml.load(f, Loader=yaml.Loader), path)
|
|
|
|
|
|
def _merge_included_bindings(binding, binding_path):
|
|
# Merges any bindings in the 'inherits:' section of 'binding' into the top
|
|
# level of 'binding'. !includes have already been processed at this point,
|
|
# and leave the data for the included binding(s) in 'inherits:'.
|
|
#
|
|
# Properties in 'binding' take precedence over properties from included
|
|
# bindings.
|
|
|
|
# Currently, we require that each !include'd file is a well-formed
|
|
# binding in itself
|
|
_check_binding(binding, binding_path)
|
|
|
|
if "inherits" in binding:
|
|
for inherited in binding.pop("inherits"):
|
|
_merge_props(
|
|
binding, _merge_included_bindings(inherited, binding_path),
|
|
None, binding_path)
|
|
|
|
return binding
|
|
|
|
|
|
def _merge_props(to_dict, from_dict, parent, binding_path):
|
|
# Recursively merges 'from_dict' into 'to_dict', to implement !include. If
|
|
# a key exists in both 'from_dict' and 'to_dict', then the value in
|
|
# 'to_dict' takes precedence.
|
|
#
|
|
# 'parent' is the name of the parent key containing 'to_dict' and
|
|
# 'from_dict', and 'binding_path' is the path to the top-level binding.
|
|
# These are used to generate errors for sketchy property overwrites.
|
|
|
|
for prop in from_dict:
|
|
if isinstance(to_dict.get(prop), dict) and \
|
|
isinstance(from_dict[prop], dict):
|
|
_merge_props(to_dict[prop], from_dict[prop], prop, binding_path)
|
|
elif prop not in to_dict:
|
|
to_dict[prop] = from_dict[prop]
|
|
elif _bad_overwrite(to_dict, from_dict, prop):
|
|
_err("{} (in '{}'): '{}' from !included file overwritten "
|
|
"('{}' replaced with '{}')".format(
|
|
binding_path, parent, prop, from_dict[prop],
|
|
to_dict[prop]))
|
|
|
|
|
|
def _bad_overwrite(to_dict, from_dict, prop):
|
|
# _merge_props() helper. Returns True in cases where it's bad that
|
|
# to_dict[prop] takes precedence over from_dict[prop].
|
|
|
|
# These are overridden deliberately
|
|
if prop in {"title", "description"}:
|
|
return False
|
|
|
|
if to_dict[prop] == from_dict[prop]:
|
|
return False
|
|
|
|
# Allow a property to be made required when it previously was optional
|
|
# without a warning
|
|
if prop == "category" and to_dict["category"] == "required" and \
|
|
from_dict["category"] == "optional":
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def _check_binding(binding, binding_path):
|
|
# Does sanity checking on 'binding'
|
|
|
|
for prop in "title", "description":
|
|
if prop not in binding:
|
|
_err("missing '{}' property in {}".format(prop, binding_path))
|
|
|
|
for prop in "title", "description":
|
|
if not isinstance(binding[prop], str) or not binding[prop]:
|
|
_err("missing, malformed, or empty '{}' in {}"
|
|
.format(prop, binding_path))
|
|
|
|
ok_top = {"title", "description", "inherits", "properties", "#cells",
|
|
"parent", "child", "sub-node"}
|
|
|
|
for prop in binding:
|
|
if prop not in ok_top:
|
|
_err("unknown key '{}' in {}, expected one of {}"
|
|
.format(prop, binding_path, ", ".join(ok_top)))
|
|
|
|
for pc in "parent", "child":
|
|
if pc in binding:
|
|
# Just 'bus:' is expected at the moment
|
|
if binding[pc].keys() != {"bus"}:
|
|
_err("expected (just) 'bus:' in '{}:' in {}"
|
|
.format(pc, binding_path))
|
|
|
|
if not isinstance(binding[pc]["bus"], str):
|
|
_err("malformed '{}: bus:' value in {}, expected string"
|
|
.format(pc, binding_path))
|
|
|
|
# Check properties
|
|
|
|
if "properties" not in binding:
|
|
return
|
|
|
|
ok_prop_keys = {"description", "type", "category", "constraint", "enum"}
|
|
ok_categories = {"required", "optional"}
|
|
|
|
for prop_name, options in binding["properties"].items():
|
|
for key in options:
|
|
if key not in ok_prop_keys:
|
|
_err("unknown setting '{}' in 'properties: {}: ...' in {}, "
|
|
"expected one of {}".format(
|
|
key, prop_name, binding_path,
|
|
", ".join(ok_prop_keys)))
|
|
|
|
if "category" in options and options["category"] not in ok_categories:
|
|
_err("unrecognized category '{}' for '{}' in 'properties' in {}, "
|
|
"expected one of {}".format(
|
|
options["category"], prop_name, binding_path,
|
|
", ".join(ok_categories)))
|
|
|
|
if "description" in options and \
|
|
not isinstance(options["description"], str):
|
|
_err("missing, malformed, or empty 'description' for '{}' in "
|
|
"'properties' in {}".format(prop_name, binding_path))
|
|
|
|
|
|
def _translate(addr, node):
|
|
# Recursively translates 'addr' on 'node' to the address space(s) of its
|
|
# parent(s), by looking at 'ranges' properties. Returns the translated
|
|
# address.
|
|
|
|
if not node.parent or "ranges" not in node.parent.props:
|
|
# No translation
|
|
return addr
|
|
|
|
if not node.parent.props["ranges"].value:
|
|
# DT spec.: "If the property is defined with an <empty> value, it
|
|
# specifies that the parent and child address space is identical, and
|
|
# no address translation is required."
|
|
#
|
|
# Treat this the same as a 'range' that explicitly does a one-to-one
|
|
# mapping, as opposed to there not being any translation.
|
|
return _translate(addr, node.parent)
|
|
|
|
# Gives the size of each component in a translation 3-tuple in 'ranges'
|
|
child_address_cells = _address_cells(node)
|
|
parent_address_cells = _address_cells(node.parent)
|
|
child_size_cells = _size_cells(node)
|
|
|
|
# Number of cells for one translation 3-tuple in 'ranges'
|
|
entry_cells = child_address_cells + parent_address_cells + child_size_cells
|
|
|
|
for raw_range in _slice(node.parent, "ranges", 4*entry_cells):
|
|
child_addr = to_num(raw_range[:4*child_address_cells])
|
|
raw_range = raw_range[4*child_address_cells:]
|
|
|
|
parent_addr = to_num(raw_range[:4*parent_address_cells])
|
|
raw_range = raw_range[4*parent_address_cells:]
|
|
|
|
child_len = to_num(raw_range)
|
|
|
|
if child_addr <= addr < child_addr + child_len:
|
|
# 'addr' is within range of a translation in 'ranges'. Recursively
|
|
# translate it and return the result.
|
|
return _translate(parent_addr + addr - child_addr, node.parent)
|
|
|
|
# 'addr' is not within range of any translation in 'ranges'
|
|
return addr
|
|
|
|
|
|
def _add_names(node, names_ident, objs):
|
|
# Helper for registering names from <foo>-names properties.
|
|
#
|
|
# node:
|
|
# Device tree node
|
|
#
|
|
# names-ident:
|
|
# The <foo> part of <foo>-names, e.g. "reg" for "reg-names"
|
|
#
|
|
# objs:
|
|
# list of objects whose .name field should be set
|
|
|
|
full_names_ident = names_ident + "-names"
|
|
|
|
if full_names_ident in node.props:
|
|
names = node.props[full_names_ident].to_strings()
|
|
if len(names) != len(objs):
|
|
_err("{} property in {} has {} strings, expected {} strings"
|
|
.format(full_names_ident, node.name, len(names), len(objs)))
|
|
|
|
for obj, name in zip(objs, names):
|
|
obj.name = name
|
|
else:
|
|
for obj in objs:
|
|
obj.name = None
|
|
|
|
|
|
def _interrupt_parent(node):
|
|
# Returns the node pointed at by the closest 'interrupt-parent', searching
|
|
# the parents of 'node'. As of writing, this behavior isn't specified in
|
|
# the DT spec., but seems to match what some .dts files except.
|
|
|
|
while node:
|
|
if "interrupt-parent" in node.props:
|
|
return node.props["interrupt-parent"].to_node()
|
|
node = node.parent
|
|
|
|
_err("{!r} has an 'interrupts' property, but neither the node nor any "
|
|
"of its parents has an 'interrupt-parent' property".format(node))
|
|
|
|
|
|
def _interrupts(node):
|
|
# Returns a list of (<controller>, <specifier>) tuples, with one tuple per
|
|
# interrupt generated by 'node'. <controller> is the destination of the
|
|
# interrupt (possibly after mapping through an 'interrupt-map'), and
|
|
# <specifier> the data associated with the interrupt (as a 'bytes' object).
|
|
|
|
# Takes precedence over 'interrupts' if both are present
|
|
if "interrupts-extended" in node.props:
|
|
prop = node.props["interrupts-extended"]
|
|
return [_map_interrupt(node, iparent, spec)
|
|
for iparent, spec in _phandle_val_list(prop, "interrupt")]
|
|
|
|
if "interrupts" in node.props:
|
|
# Treat 'interrupts' as a special case of 'interrupts-extended', with
|
|
# the same interrupt parent for all interrupts
|
|
|
|
iparent = _interrupt_parent(node)
|
|
interrupt_cells = _interrupt_cells(iparent)
|
|
|
|
return [_map_interrupt(node, iparent, raw)
|
|
for raw in _slice(node, "interrupts", 4*interrupt_cells)]
|
|
|
|
return []
|
|
|
|
|
|
def _map_interrupt(child, parent, child_spec):
|
|
# Translates an interrupt headed from 'child' to 'parent' with specifier
|
|
# 'child_spec' through any 'interrupt-map' properties. Returns a
|
|
# (<controller>, <specifier>) tuple with the final destination after
|
|
# mapping.
|
|
|
|
if "interrupt-controller" in parent.props:
|
|
return (parent, child_spec)
|
|
|
|
def own_address_cells(node):
|
|
# Used for parents pointed at by 'interrupt-map'. We can't use
|
|
# _address_cells(), because it's the #address-cells property on 'node'
|
|
# itself that matters.
|
|
|
|
address_cells = node.props.get("#address-cells")
|
|
if not address_cells:
|
|
_err("missing #address-cells on {!r} (while handling interrupt-map)"
|
|
.format(node))
|
|
return address_cells.to_num()
|
|
|
|
def spec_len_fn(node):
|
|
# Can't use _address_cells() here, because it's the #address-cells
|
|
# property on 'node' itself that matters
|
|
return 4*(own_address_cells(node) + _interrupt_cells(node))
|
|
|
|
parent, raw_spec = _map(
|
|
"interrupt", child, parent, _raw_unit_addr(child) + child_spec,
|
|
spec_len_fn)
|
|
|
|
# Strip the parent unit address part, if any
|
|
return (parent, raw_spec[4*own_address_cells(parent):])
|
|
|
|
|
|
def _gpios(node):
|
|
# Returns a dictionary that maps '<prefix>-gpios' prefixes to lists of
|
|
# (<controller>, <specifier>) tuples (possibly after mapping through an
|
|
# gpio-map). <controller> is a dtlib.Node.
|
|
|
|
res = {}
|
|
|
|
for name, prop in node.props.items():
|
|
if name.endswith("-gpios") or name == "gpios":
|
|
# Get the prefix from the property name:
|
|
# - gpios -> "" (deprecated, should have a prefix)
|
|
# - foo-gpios -> "foo"
|
|
# - etc.
|
|
prefix = name[:-5]
|
|
if prefix.endswith("-"):
|
|
prefix = prefix[:-1]
|
|
|
|
res[prefix] = [
|
|
_map_gpio(prop.node, controller, spec)
|
|
for controller, spec in _phandle_val_list(prop, "gpio")
|
|
]
|
|
|
|
return res
|
|
|
|
|
|
def _map_gpio(child, parent, child_spec):
|
|
# Returns a (<controller>, <specifier>) tuple with the final destination
|
|
# after mapping through any 'gpio-map' properties. See _map_interrupt().
|
|
|
|
if "gpio-map" not in parent.props:
|
|
return (parent, child_spec)
|
|
|
|
return _map("gpio", child, parent, child_spec,
|
|
lambda node: 4*_gpio_cells(node))
|
|
|
|
|
|
def _map(prefix, child, parent, child_spec, spec_len_fn):
|
|
# Common code for mapping through <prefix>-map properties, e.g.
|
|
# interrupt-map and gpio-map.
|
|
#
|
|
# prefix:
|
|
# The prefix, e.g. "interrupt" or "gpio"
|
|
#
|
|
# child:
|
|
# The "sender", e.g. the node with 'interrupts = <...>'
|
|
#
|
|
# parent:
|
|
# The "receiver", e.g. a node with 'interrupt-map = <...>' or
|
|
# 'interrupt-controller' (no mapping)
|
|
#
|
|
# child_spec:
|
|
# The data associated with the interrupt/GPIO/etc., as a 'bytes' object,
|
|
# e.g. <1 2> for 'foo-gpios = <&gpio1 1 2>'.
|
|
#
|
|
# spec_len_fn:
|
|
# Function called on a parent specified in a *-map property to get its
|
|
# *-cells value, e.g. #interrupt-cells
|
|
|
|
map_prop = parent.props.get(prefix + "-map")
|
|
if not map_prop:
|
|
if prefix + "-controller" not in parent.props:
|
|
_err("expected '{}-controller' property on {!r} "
|
|
"(referenced by {!r})".format(prefix, parent, child))
|
|
|
|
# No mapping
|
|
return (parent, child_spec)
|
|
|
|
masked_child_spec = _mask(prefix, child, parent, child_spec)
|
|
|
|
raw = map_prop.value
|
|
while raw:
|
|
if len(raw) < len(child_spec):
|
|
_err("bad value for {!r}, missing/truncated child specifier"
|
|
.format(map_prop))
|
|
child_spec_entry = raw[:len(child_spec)]
|
|
raw = raw[len(child_spec):]
|
|
|
|
if len(raw) < 4:
|
|
_err("bad value for {!r}, missing/truncated phandle"
|
|
.format(map_prop))
|
|
phandle = to_num(raw[:4])
|
|
raw = raw[4:]
|
|
|
|
# Parent specified in *-map
|
|
map_parent = parent.dt.phandle2node.get(phandle)
|
|
if not map_parent:
|
|
_err("bad phandle in " + repr(map_prop))
|
|
|
|
map_parent_spec_len = spec_len_fn(map_parent)
|
|
if len(raw) < map_parent_spec_len:
|
|
_err("bad value for {!r}, missing/truncated parent specifier"
|
|
.format(map_prop))
|
|
parent_spec = raw[:map_parent_spec_len]
|
|
raw = raw[map_parent_spec_len:]
|
|
|
|
# Got one *-map row. Check if it matches the child specifier.
|
|
if child_spec_entry == masked_child_spec:
|
|
# Handle *-map-pass-thru
|
|
parent_spec = _pass_thru(
|
|
prefix, child, parent, child_spec, parent_spec)
|
|
|
|
# Found match. Recursively map and return it.
|
|
return _map(prefix, parent, map_parent, parent_spec, spec_len_fn)
|
|
|
|
_err("child specifier for {!r} ({}) does not appear in {!r}"
|
|
.format(child, child_spec, map_prop))
|
|
|
|
|
|
def _mask(prefix, child, parent, child_spec):
|
|
# Common code for handling <prefix>-mask properties, e.g. interrupt-mask.
|
|
# See _map() for the parameters.
|
|
|
|
mask_prop = parent.props.get(prefix + "-map-mask")
|
|
if not mask_prop:
|
|
# No mask
|
|
return child_spec
|
|
|
|
mask = mask_prop.value
|
|
if len(mask) != len(child_spec):
|
|
_err("{!r}: expected '{}-mask' in {!r} to be {} bytes, is {} bytes"
|
|
.format(child, prefix, parent, len(child_spec), len(mask)))
|
|
|
|
return _and(child_spec, mask)
|
|
|
|
|
|
def _pass_thru(prefix, child, parent, child_spec, parent_spec):
|
|
# Common code for handling <prefix>-map-thru properties, e.g.
|
|
# interrupt-pass-thru.
|
|
#
|
|
# parent_spec:
|
|
# The parent specifier from the matched entry in the <prefix>-map
|
|
# property
|
|
#
|
|
# See _map() for the other parameters.
|
|
|
|
pass_thru_prop = parent.props.get(prefix + "-map-pass-thru")
|
|
if not pass_thru_prop:
|
|
# No pass-thru
|
|
return parent_spec
|
|
|
|
pass_thru = pass_thru_prop.value
|
|
if len(pass_thru) != len(child_spec):
|
|
_err("{!r}: expected '{}-map-pass-thru' in {!r} to be {} bytes, is {} bytes"
|
|
.format(child, prefix, parent, len(child_spec), len(pass_thru)))
|
|
|
|
res = _or(_and(child_spec, pass_thru),
|
|
_and(parent_spec, _not(pass_thru)))
|
|
|
|
# Truncate to length of parent spec.
|
|
return res[-len(parent_spec):]
|
|
|
|
|
|
def _raw_unit_addr(node):
|
|
# _map_interrupt() helper. Returns the unit address (derived from 'reg' and
|
|
# #address-cells) as a raw 'bytes'
|
|
|
|
if 'reg' not in node.props:
|
|
_err("{!r} lacks 'reg' property (needed for 'interrupt-map' unit "
|
|
"address lookup)".format(node))
|
|
|
|
addr_len = 4*_address_cells(node)
|
|
|
|
if len(node.props['reg'].value) < addr_len:
|
|
_err("{!r} has too short 'reg' property (while doing 'interrupt-map' "
|
|
"unit address lookup)".format(node))
|
|
|
|
return node.props['reg'].value[:addr_len]
|
|
|
|
|
|
def _and(b1, b2):
|
|
# Returns the bitwise AND of the two 'bytes' objects b1 and b2. Pads
|
|
# with ones on the left if the lengths are not equal.
|
|
|
|
# Pad on the left, to equal length
|
|
maxlen = max(len(b1), len(b2))
|
|
return bytes(x & y for x, y in zip(b1.rjust(maxlen, b'\xff'),
|
|
b2.rjust(maxlen, b'\xff')))
|
|
|
|
|
|
def _or(b1, b2):
|
|
# Returns the bitwise OR of the two 'bytes' objects b1 and b2. Pads with
|
|
# zeros on the left if the lengths are not equal.
|
|
|
|
# Pad on the left, to equal length
|
|
maxlen = max(len(b1), len(b2))
|
|
return bytes(x | y for x, y in zip(b1.rjust(maxlen, b'\x00'),
|
|
b2.rjust(maxlen, b'\x00')))
|
|
|
|
|
|
def _not(b):
|
|
# Returns the bitwise not of the 'bytes' object 'b'
|
|
|
|
# ANDing with 0xFF avoids negative numbers
|
|
return bytes(~x & 0xFF for x in b)
|
|
|
|
|
|
def _phandle_val_list(prop, n_cells_name):
|
|
# Parses a '<phandle> <value> <phandle> <value> ...' value. The number of
|
|
# cells that make up each <value> is derived from the node pointed at by
|
|
# the preceding <phandle>.
|
|
#
|
|
# prop:
|
|
# dtlib.Property with value to parse
|
|
#
|
|
# n_cells_name:
|
|
# The <name> part of the #<name>-cells property to look for on the nodes
|
|
# the phandles point to, e.g. "gpio" for #gpio-cells.
|
|
#
|
|
# Returns a list of (<node>, <value>) tuples, where <node> is the node
|
|
# pointed at by <phandle>.
|
|
|
|
full_n_cells_name = "#{}-cells".format(n_cells_name)
|
|
|
|
res = []
|
|
|
|
raw = prop.value
|
|
while raw:
|
|
if len(raw) < 4:
|
|
# Not enough room for phandle
|
|
_err("bad value for " + repr(prop))
|
|
phandle = to_num(raw[:4])
|
|
raw = raw[4:]
|
|
|
|
node = prop.node.dt.phandle2node.get(phandle)
|
|
if not node:
|
|
_err("bad phandle in " + repr(prop))
|
|
|
|
if full_n_cells_name not in node.props:
|
|
_err("{!r} lacks {}".format(node, full_n_cells_name))
|
|
|
|
n_cells = node.props[full_n_cells_name].to_num()
|
|
if len(raw) < 4*n_cells:
|
|
_err("missing data after phandle in " + repr(prop))
|
|
|
|
res.append((node, raw[:4*n_cells]))
|
|
raw = raw[4*n_cells:]
|
|
|
|
return res
|
|
|
|
|
|
def _address_cells(node):
|
|
# Returns the #address-cells setting for 'node', giving the number of <u32>
|
|
# cells used to encode the address in the 'reg' property
|
|
|
|
if "#address-cells" in node.parent.props:
|
|
return node.parent.props["#address-cells"].to_num()
|
|
return 2 # Default value per DT spec.
|
|
|
|
|
|
def _size_cells(node):
|
|
# Returns the #size-cells setting for 'node', giving the number of <u32>
|
|
# cells used to encode the size in the 'reg' property
|
|
|
|
if "#size-cells" in node.parent.props:
|
|
return node.parent.props["#size-cells"].to_num()
|
|
return 1 # Default value per DT spec.
|
|
|
|
|
|
def _interrupt_cells(node):
|
|
# Returns the #interrupt-cells property value on 'node', erroring out if
|
|
# 'node' has no #interrupt-cells property
|
|
|
|
if "#interrupt-cells" not in node.props:
|
|
_err("{!r} lacks #interrupt-cells".format(node))
|
|
return node.props["#interrupt-cells"].to_num()
|
|
|
|
|
|
def _gpio_cells(node):
|
|
if "#gpio-cells" not in node.props:
|
|
_err("{!r} lacks #gpio-cells".format(node))
|
|
return node.props["#gpio-cells"].to_num()
|
|
|
|
|
|
def _slice(node, prop_name, size):
|
|
# Splits node.props[prop_name].value into 'size'-sized chunks, returning a
|
|
# list of chunks. Raises EDTError if the length of the property is not
|
|
# evenly divisible by 'size'.
|
|
|
|
raw = node.props[prop_name].value
|
|
if len(raw) % size:
|
|
_err("'{}' property in {!r} has length {}, which is not evenly "
|
|
"divisible by {}".format(prop_name, node, len(raw), size))
|
|
|
|
return [raw[i:i + size] for i in range(0, len(raw), size)]
|
|
|
|
|
|
def _check_dt(dt):
|
|
# Does devicetree sanity checks. dtlib is meant to be general and
|
|
# anything-goes except for very special properties like phandle, but in
|
|
# edtlib we can be pickier.
|
|
|
|
# Check that 'status' has one of the values given in the devicetree spec.
|
|
|
|
ok_status = {"okay", "disabled", "reserved", "fail", "fail-sss"}
|
|
|
|
for node in dt.node_iter():
|
|
if "status" in node.props:
|
|
try:
|
|
status_val = node.props["status"].to_string()
|
|
except DTError as e:
|
|
# The error message gives the path
|
|
_err(str(e))
|
|
|
|
if status_val not in ok_status:
|
|
_err("unknown 'status' value \"{}\" in {} in {}, expected one "
|
|
"of {} (see the devicetree specification)"
|
|
.format(status_val, node.path, node.dt.filename,
|
|
", ".join(ok_status)))
|
|
|
|
|
|
def _err(msg):
|
|
raise EDTError(msg)
|
|
|
|
|
|
def _warn(msg):
|
|
print("warning: " + msg, file=sys.stderr)
|