828 lines
26 KiB
Python
Executable File
828 lines
26 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Copyright (c) 2019 Nordic Semiconductor ASA
|
|
# Copyright (c) 2019 Linaro Limited
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
# This script uses edtlib to generate a header file and a .conf file (both
|
|
# containing the same values) from a devicetree (.dts) file. Information from
|
|
# binding files in YAML format is used as well.
|
|
#
|
|
# Bindings are files that describe devicetree nodes. Devicetree nodes are
|
|
# usually mapped to bindings via their 'compatible = "..."' property.
|
|
#
|
|
# See the docstring/comments at the top of edtlib.py for more information.
|
|
#
|
|
# Note: Do not access private (_-prefixed) identifiers from edtlib here (and
|
|
# also note that edtlib is not meant to expose the dtlib API directly).
|
|
# Instead, think of what API you need, and add it as a public documented API in
|
|
# edtlib. This will keep this script simple.
|
|
|
|
import argparse
|
|
import os
|
|
import pathlib
|
|
import sys
|
|
|
|
import edtlib
|
|
|
|
|
|
def main():
|
|
global conf_file
|
|
global header_file
|
|
|
|
args = parse_args()
|
|
|
|
try:
|
|
edt = edtlib.EDT(args.dts, args.bindings_dirs)
|
|
except edtlib.EDTError as e:
|
|
sys.exit("devicetree error: " + str(e))
|
|
|
|
conf_file = open(args.conf_out, "w", encoding="utf-8")
|
|
header_file = open(args.header_out, "w", encoding="utf-8")
|
|
|
|
write_top_comment(edt)
|
|
|
|
active_compats = set()
|
|
|
|
for node in edt.nodes:
|
|
if node.enabled and node.matching_compat:
|
|
# Skip 'fixed-partitions' devices since they are handled by
|
|
# write_flash() and would generate extra spurious #defines
|
|
if node.matching_compat == "fixed-partitions":
|
|
continue
|
|
|
|
write_node_comment(node)
|
|
write_regs(node)
|
|
write_irqs(node)
|
|
write_props(node)
|
|
write_clocks(node)
|
|
write_spi_dev(node)
|
|
write_bus(node)
|
|
write_existence_flags(node)
|
|
|
|
active_compats.update(node.compats)
|
|
|
|
out_comment("Active compatibles (mentioned in DTS + binding found)")
|
|
for compat in sorted(active_compats):
|
|
#define DT_COMPAT_<COMPAT> 1
|
|
out("COMPAT_{}".format(str2ident(compat)), 1)
|
|
|
|
# Derived from /chosen
|
|
write_addr_size(edt, "zephyr,sram", "SRAM")
|
|
write_addr_size(edt, "zephyr,ccm", "CCM")
|
|
write_addr_size(edt, "zephyr,dtcm", "DTCM")
|
|
write_addr_size(edt, "zephyr,ipc_shm", "IPC_SHM")
|
|
|
|
write_flash(edt)
|
|
|
|
print("Devicetree configuration written to " + args.conf_out)
|
|
|
|
|
|
def parse_args():
|
|
# Returns parsed command-line arguments
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--dts", required=True, help="DTS file")
|
|
parser.add_argument("--bindings-dirs", nargs='+', required=True,
|
|
help="directory with bindings in YAML format, "
|
|
"we allow multiple")
|
|
parser.add_argument("--header-out", required=True,
|
|
help="path to write header to")
|
|
parser.add_argument("--conf-out", required=True,
|
|
help="path to write configuration file to")
|
|
|
|
return parser.parse_args()
|
|
|
|
|
|
def write_top_comment(edt):
|
|
# Writes an overview comment with misc. info at the top of the header and
|
|
# configuration file
|
|
|
|
s = """\
|
|
Generated by gen_defines.py
|
|
|
|
DTS input file:
|
|
{}
|
|
|
|
Directories with bindings:
|
|
{}
|
|
|
|
Nodes in dependency order (ordinal and path):
|
|
""".format(edt.dts_path, ", ".join(map(relativize, edt.bindings_dirs)))
|
|
|
|
for scc in edt.scc_order():
|
|
if len(scc) > 1:
|
|
err("cycle in devicetree involving "
|
|
+ ", ".join(node.path for node in scc))
|
|
s += " {0.dep_ordinal:<3} {0.path}\n".format(scc[0])
|
|
|
|
out_comment(s, blank_before=False)
|
|
|
|
|
|
def write_node_comment(node):
|
|
# Writes a comment describing 'node' to the header and configuration file
|
|
|
|
s = """\
|
|
Devicetree node:
|
|
{}
|
|
|
|
Binding (compatible = {}):
|
|
{}
|
|
|
|
Dependency Ordinal: {}
|
|
""".format(node.path, node.matching_compat, relativize(node.binding_path),
|
|
node.dep_ordinal)
|
|
|
|
if node.depends_on:
|
|
s += "\nRequires:\n"
|
|
for dep in node.depends_on:
|
|
s += " {:<3} {}\n".format(dep.dep_ordinal, dep.path)
|
|
|
|
if node.required_by:
|
|
s += "\nSupports:\n"
|
|
for req in node.required_by:
|
|
s += " {:<3} {}\n".format(req.dep_ordinal, req.path)
|
|
|
|
# Indent description by two spaces
|
|
s += "\nDescription:\n" + \
|
|
"\n".join(" " + line for line in node.description.splitlines())
|
|
|
|
out_comment(s)
|
|
|
|
|
|
def relativize(path):
|
|
# If 'path' is within $ZEPHYR_BASE, returns it relative to $ZEPHYR_BASE,
|
|
# with a "$ZEPHYR_BASE/..." hint at the start of the string. Otherwise,
|
|
# returns 'path' unchanged.
|
|
|
|
zbase = os.getenv("ZEPHYR_BASE")
|
|
if zbase is None:
|
|
return path
|
|
|
|
try:
|
|
return str("$ZEPHYR_BASE" / pathlib.Path(path).relative_to(zbase))
|
|
except ValueError:
|
|
# Not within ZEPHYR_BASE
|
|
return path
|
|
|
|
|
|
def write_regs(node):
|
|
# Writes address/size output for the registers in the node's 'reg' property
|
|
|
|
def reg_addr_name_alias(reg):
|
|
return str2ident(reg.name) + "_BASE_ADDRESS" if reg.name else None
|
|
|
|
def reg_size_name_alias(reg):
|
|
return str2ident(reg.name) + "_SIZE" if reg.name else None
|
|
|
|
for reg in node.regs:
|
|
out_dev(node, reg_addr_ident(reg), hex(reg.addr),
|
|
name_alias=reg_addr_name_alias(reg))
|
|
|
|
if reg.size:
|
|
out_dev(node, reg_size_ident(reg), reg.size,
|
|
name_alias=reg_size_name_alias(reg))
|
|
|
|
|
|
def write_props(node):
|
|
# Writes any properties defined in the "properties" section of the binding
|
|
# for the node
|
|
|
|
for prop in node.props.values():
|
|
# Skip #size-cell and other property starting with #. Also skip mapping
|
|
# properties like 'gpio-map'.
|
|
if prop.name[0] == "#" or prop.name.endswith("-map"):
|
|
continue
|
|
|
|
# See write_clocks()
|
|
if prop.name == "clocks":
|
|
continue
|
|
|
|
# edtlib provides these as well (Property.val becomes an edtlib.Node
|
|
# and a list of edtlib.Nodes, respectively). Nothing is generated for
|
|
# them currently though.
|
|
if prop.type in {"phandle", "phandles"}:
|
|
continue
|
|
|
|
# Skip properties that we handle elsewhere
|
|
if prop.name in {
|
|
"reg", "compatible", "status", "interrupts",
|
|
"interrupt-controller", "gpio-controller"
|
|
}:
|
|
continue
|
|
|
|
if prop.description is not None:
|
|
out_comment(prop.description, blank_before=False)
|
|
|
|
ident = str2ident(prop.name)
|
|
|
|
if prop.type == "boolean":
|
|
out_dev(node, ident, 1 if prop.val else 0)
|
|
elif prop.type == "string":
|
|
out_dev_s(node, ident, prop.val)
|
|
elif prop.type == "int":
|
|
out_dev(node, ident, prop.val)
|
|
elif prop.type == "array":
|
|
for i, val in enumerate(prop.val):
|
|
out_dev(node, "{}_{}".format(ident, i), val)
|
|
out_dev(node, ident,
|
|
"{" + ", ".join(map(str, prop.val)) + "}")
|
|
elif prop.type == "string-array":
|
|
for i, val in enumerate(prop.val):
|
|
out_dev_s(node, "{}_{}".format(ident, i), val)
|
|
elif prop.type == "uint8-array":
|
|
out_dev(node, ident,
|
|
"{ " + ", ".join("0x{:02x}".format(b) for b in prop.val) + " }")
|
|
elif prop.type == "phandle-array":
|
|
write_phandle_val_list(prop)
|
|
|
|
# Generate DT_..._ENUM if there's an 'enum:' key in the binding
|
|
if prop.enum_index is not None:
|
|
out_dev(node, ident + "_ENUM", prop.enum_index)
|
|
|
|
|
|
def write_bus(node):
|
|
# Generate bus-related #defines
|
|
|
|
if not node.bus:
|
|
return
|
|
|
|
if node.parent.label is None:
|
|
err("missing 'label' property on {!r}".format(node.parent))
|
|
|
|
# #define DT_<DEV-IDENT>_BUS_NAME <BUS-LABEL>
|
|
out_dev_s(node, "BUS_NAME", str2ident(node.parent.label))
|
|
|
|
for compat in node.compats:
|
|
# #define DT_<COMPAT>_BUS_<BUS-TYPE> 1
|
|
out("{}_BUS_{}".format(str2ident(compat), str2ident(node.bus)), 1)
|
|
|
|
|
|
def write_existence_flags(node):
|
|
# Generate #defines of the form
|
|
#
|
|
# #define DT_INST_<INSTANCE>_<COMPAT> 1
|
|
#
|
|
# These are flags for which devices exist.
|
|
|
|
for compat in node.compats:
|
|
out("INST_{}_{}".format(node.instance_no[compat],
|
|
str2ident(compat)), 1)
|
|
|
|
|
|
def reg_addr_ident(reg):
|
|
# Returns the identifier (e.g., macro name) to be used for the address of
|
|
# 'reg' in the output
|
|
|
|
node = reg.node
|
|
|
|
# NOTE: to maintain compat wit the old script we special case if there's
|
|
# only a single register (we drop the '_0').
|
|
if len(node.regs) > 1:
|
|
return "BASE_ADDRESS_{}".format(node.regs.index(reg))
|
|
else:
|
|
return "BASE_ADDRESS"
|
|
|
|
|
|
def reg_size_ident(reg):
|
|
# Returns the identifier (e.g., macro name) to be used for the size of
|
|
# 'reg' in the output
|
|
|
|
node = reg.node
|
|
|
|
# NOTE: to maintain compat wit the old script we special case if there's
|
|
# only a single register (we drop the '_0').
|
|
if len(node.regs) > 1:
|
|
return "SIZE_{}".format(node.regs.index(reg))
|
|
else:
|
|
return "SIZE"
|
|
|
|
|
|
def dev_ident(node):
|
|
# Returns an identifier for the device given by 'node'. Used when building
|
|
# e.g. macro names.
|
|
|
|
# TODO: Handle PWM on STM
|
|
# TODO: Better document the rules of how we generate things
|
|
|
|
ident = ""
|
|
|
|
if node.bus:
|
|
ident += "{}_{:X}_".format(
|
|
str2ident(node.parent.matching_compat), node.parent.unit_addr)
|
|
|
|
ident += "{}_".format(str2ident(node.matching_compat))
|
|
|
|
if node.unit_addr is not None:
|
|
ident += "{:X}".format(node.unit_addr)
|
|
elif node.parent.unit_addr is not None:
|
|
ident += "{:X}_{}".format(node.parent.unit_addr, str2ident(node.name))
|
|
else:
|
|
# This is a bit of a hack
|
|
ident += "{}".format(str2ident(node.name))
|
|
|
|
return ident
|
|
|
|
|
|
def dev_aliases(node):
|
|
# Returns a list of aliases for the device given by 'node', used e.g. when
|
|
# building macro names
|
|
|
|
return dev_path_aliases(node) + dev_instance_aliases(node)
|
|
|
|
|
|
def dev_path_aliases(node):
|
|
# Returns a list of aliases for the device given by 'node', based on the
|
|
# aliases registered for it, in the /aliases node. Used when building e.g.
|
|
# macro names.
|
|
|
|
if node.matching_compat is None:
|
|
return []
|
|
|
|
compat_s = str2ident(node.matching_compat)
|
|
|
|
aliases = []
|
|
for alias in node.aliases:
|
|
aliases.append("ALIAS_{}".format(str2ident(alias)))
|
|
# TODO: See if we can remove or deprecate this form
|
|
aliases.append("{}_{}".format(compat_s, str2ident(alias)))
|
|
|
|
return aliases
|
|
|
|
|
|
def dev_instance_aliases(node):
|
|
# Returns a list of aliases for the device given by 'node', based on the
|
|
# instance number of the device (based on how many instances of that
|
|
# particular device there are).
|
|
#
|
|
# This is a list since a device can have multiple 'compatible' strings,
|
|
# each with their own instance number.
|
|
|
|
return ["INST_{}_{}".format(node.instance_no[compat], str2ident(compat))
|
|
for compat in node.compats]
|
|
|
|
|
|
def write_addr_size(edt, prop_name, prefix):
|
|
# Writes <prefix>_BASE_ADDRESS and <prefix>_SIZE for the node pointed at by
|
|
# the /chosen property named 'prop_name', if it exists
|
|
|
|
node = edt.chosen_node(prop_name)
|
|
if not node:
|
|
return
|
|
|
|
if not node.regs:
|
|
err("missing 'reg' property in node pointed at by /chosen/{} ({!r})"
|
|
.format(prop_name, node))
|
|
|
|
out_comment("/chosen/{} ({})".format(prop_name, node.path))
|
|
out("{}_BASE_ADDRESS".format(prefix), hex(node.regs[0].addr))
|
|
out("{}_SIZE".format(prefix), node.regs[0].size//1024)
|
|
|
|
|
|
def write_flash(edt):
|
|
# Writes flash-related output
|
|
|
|
write_flash_node(edt)
|
|
write_code_partition(edt)
|
|
|
|
flash_index = 0
|
|
for node in edt.nodes:
|
|
if node.name.startswith("partition@"):
|
|
write_flash_partition(node, flash_index)
|
|
flash_index += 1
|
|
|
|
if flash_index != 0:
|
|
out_comment("Number of flash partitions")
|
|
out("FLASH_AREA_NUM", flash_index)
|
|
|
|
|
|
def write_flash_node(edt):
|
|
# Writes output for the top-level flash node pointed at by
|
|
# zephyr,flash in /chosen
|
|
|
|
node = edt.chosen_node("zephyr,flash")
|
|
|
|
out_comment("/chosen/zephyr,flash ({})"
|
|
.format(node.path if node else "missing"))
|
|
|
|
if not node:
|
|
# No flash node. Write dummy values.
|
|
out("FLASH_BASE_ADDRESS", 0)
|
|
out("FLASH_SIZE", 0)
|
|
return
|
|
|
|
if len(node.regs) != 1:
|
|
err("expected zephyr,flash to have a single register, has {}"
|
|
.format(len(node.regs)))
|
|
|
|
if node.bus == "spi" and len(node.parent.regs) == 2:
|
|
reg = node.parent.regs[1] # QSPI flash
|
|
else:
|
|
reg = node.regs[0]
|
|
|
|
out("FLASH_BASE_ADDRESS", hex(reg.addr))
|
|
if reg.size:
|
|
out("FLASH_SIZE", reg.size//1024)
|
|
|
|
if "erase-block-size" in node.props:
|
|
out("FLASH_ERASE_BLOCK_SIZE", node.props["erase-block-size"].val)
|
|
|
|
if "write-block-size" in node.props:
|
|
out("FLASH_WRITE_BLOCK_SIZE", node.props["write-block-size"].val)
|
|
|
|
|
|
def write_code_partition(edt):
|
|
# Writes output for the node pointed at by zephyr,code-partition in /chosen
|
|
|
|
node = edt.chosen_node("zephyr,code-partition")
|
|
|
|
out_comment("/chosen/zephyr,code-partition ({})"
|
|
.format(node.path if node else "missing"))
|
|
|
|
if not node:
|
|
# No code partition. Write dummy values.
|
|
out("CODE_PARTITION_OFFSET", 0)
|
|
out("CODE_PARTITION_SIZE", 0)
|
|
return
|
|
|
|
if not node.regs:
|
|
err("missing 'regs' property on {!r}".format(node))
|
|
|
|
out("CODE_PARTITION_OFFSET", node.regs[0].addr)
|
|
out("CODE_PARTITION_SIZE", node.regs[0].size)
|
|
|
|
|
|
def write_flash_partition(partition_node, index):
|
|
out_comment("Flash partition at " + partition_node.path)
|
|
|
|
if partition_node.label is None:
|
|
err("missing 'label' property on {!r}".format(partition_node))
|
|
|
|
# Generate label-based identifiers
|
|
write_flash_partition_prefix(
|
|
"FLASH_AREA_" + str2ident(partition_node.label), partition_node, index)
|
|
|
|
# Generate index-based identifiers
|
|
write_flash_partition_prefix(
|
|
"FLASH_AREA_{}".format(index), partition_node, index)
|
|
|
|
|
|
def write_flash_partition_prefix(prefix, partition_node, index):
|
|
# write_flash_partition() helper. Generates identifiers starting with
|
|
# 'prefix'.
|
|
|
|
out("{}_ID".format(prefix), index)
|
|
|
|
out("{}_READ_ONLY".format(prefix), 1 if partition_node.read_only else 0)
|
|
|
|
for i, reg in enumerate(partition_node.regs):
|
|
# Also add aliases that point to the first sector (TODO: get rid of the
|
|
# aliases?)
|
|
out("{}_OFFSET_{}".format(prefix, i), reg.addr,
|
|
aliases=["{}_OFFSET".format(prefix)] if i == 0 else [])
|
|
out("{}_SIZE_{}".format(prefix, i), reg.size,
|
|
aliases=["{}_SIZE".format(prefix)] if i == 0 else [])
|
|
|
|
controller = partition_node.flash_controller
|
|
if controller.label is not None:
|
|
out_s("{}_DEV".format(prefix), controller.label)
|
|
|
|
|
|
def write_irqs(node):
|
|
# Writes IRQ num and data for the interrupts in the node's 'interrupt'
|
|
# property
|
|
|
|
def irq_name_alias(irq, cell_name):
|
|
if not irq.name:
|
|
return None
|
|
|
|
alias = "IRQ_{}".format(str2ident(irq.name))
|
|
if cell_name != "irq":
|
|
alias += "_" + str2ident(cell_name)
|
|
return alias
|
|
|
|
def map_arm_gic_irq_type(irq, irq_num):
|
|
# Maps ARM GIC IRQ (type)+(index) combo to linear IRQ number
|
|
if "type" not in irq.data:
|
|
err("Expected binding for {!r} to have 'type' in interrupt-cells"
|
|
.format(irq.controller))
|
|
irq_type = irq.data["type"]
|
|
|
|
if irq_type == 0: # GIC_SPI
|
|
return irq_num + 32
|
|
if irq_type == 1: # GIC_PPI
|
|
return irq_num + 16
|
|
err("Invalid interrupt type specified for {!r}"
|
|
.format(irq))
|
|
|
|
def encode_zephyr_multi_level_irq(irq, irq_num):
|
|
# See doc/reference/kernel/other/interrupts.rst for details
|
|
# on how this encoding works
|
|
|
|
irq_ctrl = irq.controller
|
|
# Look for interrupt controller parent until we have none
|
|
while irq_ctrl.interrupts:
|
|
irq_num = (irq_num + 1) << 8
|
|
if "irq" not in irq_ctrl.interrupts[0].data:
|
|
err("Expected binding for {!r} to have 'irq' in interrupt-cells"
|
|
.format(irq_ctrl))
|
|
irq_num |= irq_ctrl.interrupts[0].data["irq"]
|
|
irq_ctrl = irq_ctrl.interrupts[0].controller
|
|
return irq_num
|
|
|
|
for irq_i, irq in enumerate(node.interrupts):
|
|
for cell_name, cell_value in irq.data.items():
|
|
ident = "IRQ_{}".format(irq_i)
|
|
if cell_name == "irq":
|
|
if "arm,gic" in irq.controller.compats:
|
|
cell_value = map_arm_gic_irq_type(irq, cell_value)
|
|
cell_value = encode_zephyr_multi_level_irq(irq, cell_value)
|
|
else:
|
|
ident += "_" + str2ident(cell_name)
|
|
|
|
out_dev(node, ident, cell_value,
|
|
name_alias=irq_name_alias(irq, cell_name))
|
|
|
|
|
|
def write_spi_dev(node):
|
|
# Writes SPI device GPIO chip select data if there is any
|
|
|
|
cs_gpio = edtlib.spi_dev_cs_gpio(node)
|
|
if cs_gpio is not None:
|
|
write_phandle_val_list_entry(node, cs_gpio, None, "CS_GPIOS")
|
|
|
|
|
|
def write_phandle_val_list(prop):
|
|
# Writes output for a phandle/value list, e.g.
|
|
#
|
|
# pwms = <&pwm-ctrl-1 10 20
|
|
# &pwm-ctrl-2 30 40>;
|
|
#
|
|
# prop:
|
|
# phandle/value Property instance.
|
|
#
|
|
# If only one entry appears in 'prop' (the example above has two), the
|
|
# generated identifier won't get a '_0' suffix, and the '_COUNT' and
|
|
# group initializer are skipped too.
|
|
#
|
|
# The base identifier is derived from the property name. For example, 'pwms = ...'
|
|
# generates output like this:
|
|
#
|
|
# #define <device prefix>_PWMS_CONTROLLER_0 "PWM_0" (name taken from 'label = ...')
|
|
# #define <device prefix>_PWMS_CHANNEL_0 123 (name taken from *-cells in binding)
|
|
# #define <device prefix>_PWMS_0 {"PWM_0", 123}
|
|
# #define <device prefix>_PWMS_CONTROLLER_1 "PWM_1"
|
|
# #define <device prefix>_PWMS_CHANNEL_1 456
|
|
# #define <device prefix>_PWMS_1 {"PWM_1", 456}
|
|
# #define <device prefix>_PWMS_COUNT 2
|
|
# #define <device prefix>_PWMS {<device prefix>_PWMS_0, <device prefix>_PWMS_1}
|
|
# ...
|
|
|
|
# pwms -> PWMS
|
|
# foo-gpios -> FOO_GPIOS
|
|
ident = str2ident(prop.name)
|
|
|
|
initializer_vals = []
|
|
for i, entry in enumerate(prop.val):
|
|
initializer_vals.append(write_phandle_val_list_entry(
|
|
prop.node, entry, i if len(prop.val) > 1 else None, ident))
|
|
|
|
if len(prop.val) > 1:
|
|
out_dev(prop.node, ident + "_COUNT", len(initializer_vals))
|
|
out_dev(prop.node, ident, "{" + ", ".join(initializer_vals) + "}")
|
|
|
|
|
|
def write_phandle_val_list_entry(node, entry, i, ident):
|
|
# write_phandle_val_list() helper. We could get rid of it if it wasn't for
|
|
# write_spi_dev(). Adds 'i' as an index to identifiers unless it's None.
|
|
#
|
|
# 'entry' is an edtlib.ControllerAndData instance.
|
|
#
|
|
# Returns the identifier for the macro that provides the
|
|
# initializer for the entire entry.
|
|
|
|
initializer_vals = []
|
|
if entry.controller.label is not None:
|
|
ctrl_ident = ident + "_CONTROLLER" # e.g. PWMS_CONTROLLER
|
|
if entry.name:
|
|
name_alias = str2ident(entry.name) + "_" + ctrl_ident
|
|
else:
|
|
name_alias = None
|
|
# Ugly backwards compatibility hack. Only add the index if there's
|
|
# more than one entry.
|
|
if i is not None:
|
|
ctrl_ident += "_{}".format(i)
|
|
initializer_vals.append(quote_str(entry.controller.label))
|
|
out_dev_s(node, ctrl_ident, entry.controller.label, name_alias)
|
|
|
|
for cell, val in entry.data.items():
|
|
cell_ident = ident + "_" + str2ident(cell) # e.g. PWMS_CHANNEL
|
|
if entry.name:
|
|
# From e.g. 'pwm-names = ...'
|
|
name_alias = str2ident(entry.name) + "_" + cell_ident
|
|
else:
|
|
name_alias = None
|
|
# Backwards compatibility (see above)
|
|
if i is not None:
|
|
cell_ident += "_{}".format(i)
|
|
out_dev(node, cell_ident, val, name_alias)
|
|
|
|
initializer_vals += entry.data.values()
|
|
|
|
initializer_ident = ident
|
|
if entry.name:
|
|
name_alias = initializer_ident + "_" + str2ident(entry.name)
|
|
else:
|
|
name_alias = None
|
|
if i is not None:
|
|
initializer_ident += "_{}".format(i)
|
|
return out_dev(node, initializer_ident,
|
|
"{" + ", ".join(map(str, initializer_vals)) + "}",
|
|
name_alias)
|
|
|
|
|
|
def write_clocks(node):
|
|
# Writes clock information.
|
|
#
|
|
# Most of this ought to be handled in write_props(), but the identifiers
|
|
# that get generated for 'clocks' are inconsistent with the with other
|
|
# 'phandle-array' properties.
|
|
#
|
|
# See https://github.com/zephyrproject-rtos/zephyr/pull/19327#issuecomment-534081845.
|
|
|
|
if "clocks" not in node.props:
|
|
return
|
|
|
|
for clock_i, clock in enumerate(node.props["clocks"].val):
|
|
controller = clock.controller
|
|
|
|
if controller.label is not None:
|
|
out_dev_s(node, "CLOCK_CONTROLLER", controller.label)
|
|
|
|
for name, val in clock.data.items():
|
|
if clock_i == 0:
|
|
clk_name_alias = "CLOCK_" + str2ident(name)
|
|
else:
|
|
clk_name_alias = None
|
|
|
|
out_dev(node, "CLOCK_{}_{}".format(str2ident(name), clock_i), val,
|
|
name_alias=clk_name_alias)
|
|
|
|
if "fixed-clock" not in controller.compats:
|
|
continue
|
|
|
|
if "clock-frequency" not in controller.props:
|
|
err("{!r} is a 'fixed-clock' but lacks a 'clock-frequency' "
|
|
"property".format(controller))
|
|
|
|
out_dev(node, "CLOCKS_CLOCK_FREQUENCY",
|
|
controller.props["clock-frequency"].val)
|
|
|
|
|
|
def str2ident(s):
|
|
# Converts 's' to a form suitable for (part of) an identifier
|
|
|
|
return s.replace("-", "_") \
|
|
.replace(",", "_") \
|
|
.replace("@", "_") \
|
|
.replace("/", "_") \
|
|
.replace(".", "_") \
|
|
.replace("+", "PLUS") \
|
|
.upper()
|
|
|
|
|
|
def out_dev(node, ident, val, name_alias=None):
|
|
# Writes an
|
|
#
|
|
# <device prefix>_<ident> = <val>
|
|
#
|
|
# assignment, along with a set of
|
|
#
|
|
# <device alias>_<ident>
|
|
#
|
|
# aliases, for each device alias. If 'name_alias' (a string) is passed,
|
|
# then these additional aliases are generated:
|
|
#
|
|
# <device prefix>_<name alias>
|
|
# <device alias>_<name alias> (for each device alias)
|
|
#
|
|
# 'name_alias' is used for reg-names and the like.
|
|
#
|
|
# Returns the identifier used for the macro that provides the value
|
|
# for 'ident' within 'node', e.g. DT_MFG_MODEL_CTL_GPIOS_PIN.
|
|
|
|
dev_prefix = dev_ident(node)
|
|
|
|
aliases = [alias + "_" + ident for alias in dev_aliases(node)]
|
|
if name_alias is not None:
|
|
aliases.append(dev_prefix + "_" + name_alias)
|
|
aliases += [alias + "_" + name_alias for alias in dev_aliases(node)]
|
|
|
|
return out(dev_prefix + "_" + ident, val, aliases)
|
|
|
|
|
|
def out_dev_s(node, ident, s, name_alias=None):
|
|
# Like out_dev(), but emits 's' as a string literal
|
|
#
|
|
# Returns the generated macro name for 'ident'.
|
|
|
|
return out_dev(node, ident, quote_str(s), name_alias)
|
|
|
|
|
|
def out_s(ident, val):
|
|
# Like out(), but puts quotes around 'val' and escapes any double
|
|
# quotes and backslashes within it
|
|
#
|
|
# Returns the generated macro name for 'ident'.
|
|
|
|
return out(ident, quote_str(val))
|
|
|
|
|
|
def out(ident, val, aliases=()):
|
|
# Writes '#define <ident> <val>' to the header and '<ident>=<val>' to the
|
|
# the configuration file.
|
|
#
|
|
# Also writes any aliases listed in 'aliases' (an iterable). For the
|
|
# header, these look like '#define <alias> <ident>'. For the configuration
|
|
# file, the value is just repeated as '<alias>=<val>' for each alias.
|
|
#
|
|
# Returns the generated macro name for 'ident'.
|
|
|
|
print("#define DT_{:40} {}".format(ident, val), file=header_file)
|
|
primary_ident = "DT_{}".format(ident)
|
|
|
|
# Exclude things that aren't single token values from .conf. At
|
|
# the moment the only such items are unquoted string
|
|
# representations of initializer lists, which begin with a curly
|
|
# brace.
|
|
output_to_conf = not (isinstance(val, str) and val.startswith("{"))
|
|
if output_to_conf:
|
|
print("{}={}".format(primary_ident, val), file=conf_file)
|
|
|
|
for alias in aliases:
|
|
if alias != ident:
|
|
print("#define DT_{:40} DT_{}".format(alias, ident),
|
|
file=header_file)
|
|
if output_to_conf:
|
|
# For the configuration file, the value is just repeated for all
|
|
# the aliases
|
|
print("DT_{}={}".format(alias, val), file=conf_file)
|
|
|
|
return primary_ident
|
|
|
|
|
|
def out_comment(s, blank_before=True):
|
|
# Writes 's' as a comment to the header and configuration file. 's' is
|
|
# allowed to have multiple lines. blank_before=True adds a blank line
|
|
# before the comment.
|
|
|
|
if blank_before:
|
|
print(file=header_file)
|
|
print(file=conf_file)
|
|
|
|
if "\n" in s:
|
|
# Format multi-line comments like
|
|
#
|
|
# /*
|
|
# * first line
|
|
# * second line
|
|
# *
|
|
# * empty line before this line
|
|
# */
|
|
res = ["/*"]
|
|
for line in s.splitlines():
|
|
# Avoid an extra space after '*' for empty lines. They turn red in
|
|
# Vim if space error checking is on, which is annoying.
|
|
res.append(" *" if not line.strip() else " * " + line)
|
|
res.append(" */")
|
|
print("\n".join(res), file=header_file)
|
|
else:
|
|
# Format single-line comments like
|
|
#
|
|
# /* foo bar */
|
|
print("/* " + s + " */", file=header_file)
|
|
|
|
print("\n".join("# " + line for line in s.splitlines()), file=conf_file)
|
|
|
|
|
|
def escape(s):
|
|
# Backslash-escapes any double quotes and backslashes in 's'
|
|
|
|
# \ must be escaped before " to avoid double escaping
|
|
return s.replace("\\", "\\\\").replace('"', '\\"')
|
|
|
|
|
|
def quote_str(s):
|
|
# Puts quotes around 's' and escapes any double quotes and
|
|
# backslashes within it
|
|
|
|
return '"{}"'.format(escape(s))
|
|
|
|
|
|
def err(s):
|
|
raise Exception(s)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|