mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-02-18 19:03:16 +00:00
Devicetree DTS alias support (#483)
* **New Features** * Phandle support for device-to-device property references. * Recognition of #define-style declarations in device trees. * Device nodes may include optional aliases alongside node names. * **Improvements** * Flatter, consistent device hierarchy processing for generation. * Error and log messages now reference node names for clearer diagnostics. * **Other** * Added a static ESP32-based device tree and minor DTS comment updates.
This commit is contained in:
parent
ecc0a9c076
commit
626d0d9776
@ -10,18 +10,25 @@ def write_include(file, include: IncludeC, verbose: bool):
|
||||
file.write(include.statement)
|
||||
file.write('\n')
|
||||
|
||||
def get_device_identifier_safe(device: Device):
|
||||
if device.identifier == "/":
|
||||
def write_define(file, define: DefineC, verbose: bool):
|
||||
if verbose:
|
||||
print("Processing define:")
|
||||
print(f" {define.statement}")
|
||||
file.write(define.statement)
|
||||
file.write('\n')
|
||||
|
||||
def get_device_node_name_safe(device: Device):
|
||||
if device.node_name == "/":
|
||||
return "root"
|
||||
else:
|
||||
return device.identifier
|
||||
return device.node_name.replace("-", "_")
|
||||
|
||||
def get_device_type_name(device: Device, bindings: list[Binding]):
|
||||
device_binding = find_device_binding(device, bindings)
|
||||
if device_binding is None:
|
||||
raise Exception(f"Binding not found for {device.identifier}")
|
||||
raise Exception(f"Binding not found for {device.node_name}")
|
||||
if device_binding.compatible is None:
|
||||
raise Exception(f"Couldn't find compatible binding for {device.identifier}")
|
||||
raise Exception(f"Couldn't find compatible binding for {device.node_name}")
|
||||
compatible_safe = device_binding.compatible.split(",")[-1]
|
||||
return compatible_safe.replace("-", "_")
|
||||
|
||||
@ -34,7 +41,7 @@ def find_device_property(device: Device, name: str) -> DeviceProperty:
|
||||
def find_device_binding(device: Device, bindings: list[Binding]) -> Binding:
|
||||
compatible_property = find_device_property(device, "compatible")
|
||||
if compatible_property is None:
|
||||
raise Exception(f"property 'compatible' not found in device {device.identifier}")
|
||||
raise Exception(f"property 'compatible' not found in device {device.node_name}")
|
||||
for binding in bindings:
|
||||
if binding.compatible == compatible_property.value:
|
||||
return binding
|
||||
@ -46,7 +53,13 @@ def find_binding(compatible: str, bindings: list[Binding]) -> Binding:
|
||||
return binding
|
||||
return None
|
||||
|
||||
def property_to_string(property: DeviceProperty) -> str:
|
||||
def find_phandle(devices: list[Device], phandle: str):
|
||||
for device in devices:
|
||||
if device.node_name == phandle or device.node_alias == phandle:
|
||||
return f"&{get_device_node_name_safe(device)}"
|
||||
raise Exception(f"phandle '{phandle}' not found in device tree")
|
||||
|
||||
def property_to_string(property: DeviceProperty, devices: list[Device]) -> str:
|
||||
type = property.type
|
||||
if type == "value":
|
||||
return property.value
|
||||
@ -54,16 +67,18 @@ def property_to_string(property: DeviceProperty) -> str:
|
||||
return f"\"{property.value}\""
|
||||
elif type == "values":
|
||||
return "{ " + ",".join(property.value) + " }"
|
||||
elif type == "phandle":
|
||||
return find_phandle(devices, property.value)
|
||||
else:
|
||||
raise Exception(f"property_to_string() has an unsupported type: {type}")
|
||||
|
||||
def resolve_parameters_from_bindings(device: Device, bindings: list[Binding]) -> list:
|
||||
def resolve_parameters_from_bindings(device: Device, bindings: list[Binding], devices: list[Device]) -> list:
|
||||
compatible_property = find_device_property(device, "compatible")
|
||||
if compatible_property is None:
|
||||
raise Exception(f"Cannot find 'compatible' property for {device.identifier}")
|
||||
raise Exception(f"Cannot find 'compatible' property for {device.node_name}")
|
||||
device_binding = find_binding(compatible_property.value, bindings)
|
||||
if device_binding is None:
|
||||
raise Exception(f"Binding not found for {device.identifier} and compatible '{compatible_property.value}'")
|
||||
raise Exception(f"Binding not found for {device.node_name} and compatible '{compatible_property.value}'")
|
||||
# Filter out system properties
|
||||
binding_properties = []
|
||||
for property in device_binding.properties:
|
||||
@ -75,19 +90,19 @@ def resolve_parameters_from_bindings(device: Device, bindings: list[Binding]) ->
|
||||
device_property = find_device_property(device, binding_property.name)
|
||||
if device_property is None:
|
||||
if binding_property.required:
|
||||
raise Exception(f"device {device.identifier} doesn't have property '{binding_property.name}'")
|
||||
raise Exception(f"device {device.node_name} doesn't have property '{binding_property.name}'")
|
||||
else:
|
||||
result[index] = '0'
|
||||
else:
|
||||
result[index] = property_to_string(device_property)
|
||||
result[index] = property_to_string(device_property, devices)
|
||||
return result
|
||||
|
||||
def write_config(file, device: Device, bindings: list[Binding], type_name: str):
|
||||
device_identifier = get_device_identifier_safe(device)
|
||||
def write_config(file, device: Device, bindings: list[Binding], devices: list[Device], type_name: str):
|
||||
node_name = get_device_node_name_safe(device)
|
||||
config_type = f"{type_name}_config_dt"
|
||||
config_variable_name = f"{device_identifier}_config"
|
||||
config_variable_name = f"{node_name}_config"
|
||||
file.write(f"static const {config_type} {config_variable_name}" " = {\n")
|
||||
config_params = resolve_parameters_from_bindings(device, bindings)
|
||||
config_params = resolve_parameters_from_bindings(device, bindings, devices)
|
||||
# Indent all params
|
||||
for index, config_param in enumerate(config_params):
|
||||
config_params[index] = f"\t{config_param}"
|
||||
@ -97,50 +112,63 @@ def write_config(file, device: Device, bindings: list[Binding], type_name: str):
|
||||
file.write(f"{config_params_joined}\n")
|
||||
file.write("};\n\n")
|
||||
|
||||
def write_device_structs(file, device: Device, parent_device: Device, bindings: list[Binding], verbose: bool):
|
||||
def write_device_structs(file, device: Device, parent_device: Device, bindings: list[Binding], devices: list[Device], verbose: bool):
|
||||
if verbose:
|
||||
print(f"Writing device struct for '{device.identifier}'")
|
||||
print(f"Writing device struct for '{device.node_name}'")
|
||||
# Assemble some pre-requisites
|
||||
type_name = get_device_type_name(device, bindings)
|
||||
compatible_property = find_device_property(device, "compatible")
|
||||
if compatible_property is None:
|
||||
raise Exception(f"Cannot find 'compatible' property for {device.identifier}")
|
||||
identifier = get_device_identifier_safe(device)
|
||||
config_variable_name = f"{identifier}_config"
|
||||
raise Exception(f"Cannot find 'compatible' property for {device.node_name}")
|
||||
node_name = get_device_node_name_safe(device)
|
||||
config_variable_name = f"{node_name}_config"
|
||||
if parent_device is not None:
|
||||
parent_identifier = get_device_identifier_safe(parent_device)
|
||||
parent_value = f"&{parent_identifier}"
|
||||
parent_node_name = get_device_node_name_safe(parent_device)
|
||||
parent_value = f"&{parent_node_name}"
|
||||
else:
|
||||
parent_value = "NULL"
|
||||
# Write config struct
|
||||
write_config(file, device, bindings, type_name)
|
||||
write_config(file, device, bindings, devices, type_name)
|
||||
# Write device struct
|
||||
file.write(f"static struct Device {identifier}" " = {\n")
|
||||
file.write(f"\t.name = \"{device.identifier}\",\n") # Use original name
|
||||
file.write(f"static struct Device {node_name}" " = {\n")
|
||||
file.write(f"\t.name = \"{device.node_name}\",\n") # Use original name
|
||||
file.write(f"\t.config = &{config_variable_name},\n")
|
||||
file.write(f"\t.parent = {parent_value},\n")
|
||||
file.write("};\n\n")
|
||||
# Child devices
|
||||
for child_device in device.devices:
|
||||
write_device_structs(file, child_device, device, bindings, verbose)
|
||||
write_device_structs(file, child_device, device, bindings, devices, verbose)
|
||||
|
||||
def write_device_init(file, device: Device, bindings: list[Binding], verbose: bool):
|
||||
if verbose:
|
||||
print(f"Processing device init code for '{device.identifier}'")
|
||||
print(f"Processing device init code for '{device.node_name}'")
|
||||
# Assemble some pre-requisites
|
||||
compatible_property = find_device_property(device, "compatible")
|
||||
if compatible_property is None:
|
||||
raise Exception(f"Cannot find 'compatible' property for {device.identifier}")
|
||||
raise Exception(f"Cannot find 'compatible' property for {device.node_name}")
|
||||
# Type & instance names
|
||||
identifier = get_device_identifier_safe(device)
|
||||
device_variable = identifier
|
||||
node_name = get_device_node_name_safe(device)
|
||||
device_variable = node_name
|
||||
# Write device struct
|
||||
file.write("\t{ " f"&{device_variable}, \"{compatible_property.value}\"" " },\n")
|
||||
# Write children
|
||||
for child_device in device.devices:
|
||||
write_device_init(file, child_device, bindings, verbose)
|
||||
|
||||
# Walk the tree and gather all devices
|
||||
def gather_devices(device: Device, output: list[Device]):
|
||||
output.append(device)
|
||||
for child_device in device.devices:
|
||||
gather_devices(child_device, output)
|
||||
|
||||
def generate_devicetree_c(filename: str, items: list[object], bindings: list[Binding], verbose: bool):
|
||||
# Create a cache for looking up device names and aliases easily
|
||||
# We still want to traverse it as a tree during code generation because of parent-setting
|
||||
devices = list()
|
||||
for item in items:
|
||||
if type(item) is Device:
|
||||
gather_devices(item, devices)
|
||||
|
||||
with open(filename, "w") as file:
|
||||
file.write(dedent('''\
|
||||
// Default headers
|
||||
@ -152,12 +180,14 @@ def generate_devicetree_c(filename: str, items: list[object], bindings: list[Bin
|
||||
for item in items:
|
||||
if type(item) is IncludeC:
|
||||
write_include(file, item, verbose)
|
||||
elif type(item) is DefineC:
|
||||
write_define(file, item, verbose)
|
||||
file.write("\n")
|
||||
|
||||
# Then write all devices
|
||||
for item in items:
|
||||
if type(item) is Device:
|
||||
write_device_structs(file, item, None, bindings, verbose)
|
||||
write_device_structs(file, item, None, bindings, devices, verbose)
|
||||
# Init function body start
|
||||
file.write("struct CompatibleDevice devicetree_devices[] = {\n")
|
||||
# Init function body logic
|
||||
|
||||
@ -21,6 +21,7 @@ BOOLEAN: "true" | "false"
|
||||
// Main
|
||||
|
||||
INCLUDE_C: /#include <[\w\/.\-]+>/
|
||||
DEFINE_C: /#define[^\n]+/
|
||||
|
||||
PROPERTY_NAME: /#?[a-zA-Z0-9_\-,]+/
|
||||
|
||||
@ -29,20 +30,21 @@ QUOTED_TEXT: QUOTE /[^"]+/ QUOTE
|
||||
quoted_text_array: QUOTED_TEXT ("," " "* QUOTED_TEXT)+
|
||||
HEX_NUMBER: "0x" HEXDIGIT+
|
||||
NUMBER: SIGNED_NUMBER | HEX_NUMBER
|
||||
PHANDLE: /&[0-9a-zA-Z\-]+/
|
||||
PHANDLE: /&[0-9a-zA-Z_\-]+/
|
||||
C_VARIABLE: /[0-9a-zA-Z_]+/
|
||||
VALUE: NUMBER | PHANDLE | C_VARIABLE
|
||||
value: VALUE
|
||||
values: VALUE+
|
||||
array: NUMBER+
|
||||
|
||||
property_value: quoted_text_array | QUOTED_TEXT | "<" value ">" | "<" values ">" | "[" array "]"
|
||||
property_value: quoted_text_array | QUOTED_TEXT | "<" value ">" | "<" values ">" | "[" array "]" | PHANDLE
|
||||
device_property: PROPERTY_NAME ["=" property_value] ";"
|
||||
|
||||
DEVICE_IDENTIFIER: /[a-zA-Z0-9_\-\/@]+/
|
||||
NODE_ALIAS: /[a-zA-Z0-9_\-\/@]+/
|
||||
NODE_NAME: /[a-zA-Z0-9_\-\/@]+/
|
||||
|
||||
device: DEVICE_IDENTIFIER "{" (device | device_property)* "};"
|
||||
device: (NODE_ALIAS ":")? NODE_NAME "{" (device | device_property)* "};"
|
||||
|
||||
dts_version: /[0-9a-zA-Z\-]+/
|
||||
|
||||
start: "/" dts_version "/;" INCLUDE_C* device+
|
||||
start: "/" dts_version "/;" (INCLUDE_C | DEFINE_C)* device+
|
||||
|
||||
@ -6,7 +6,8 @@ class DtsVersion:
|
||||
|
||||
@dataclass
|
||||
class Device:
|
||||
identifier: str
|
||||
node_name: str
|
||||
node_alias: str
|
||||
properties: list
|
||||
devices: list
|
||||
|
||||
@ -25,6 +26,10 @@ class PropertyValue:
|
||||
class IncludeC:
|
||||
statement: str
|
||||
|
||||
@dataclass
|
||||
class DefineC:
|
||||
statement: str
|
||||
|
||||
@dataclass
|
||||
class BindingProperty:
|
||||
name: str
|
||||
|
||||
@ -3,6 +3,7 @@ from typing import List
|
||||
from lark import Transformer
|
||||
from lark import Token
|
||||
from source.models import *
|
||||
from dataclasses import dataclass
|
||||
|
||||
def flatten_token_array(tokens: List[Token], name: str):
|
||||
result_list = list()
|
||||
@ -10,6 +11,11 @@ def flatten_token_array(tokens: List[Token], name: str):
|
||||
result_list.append(token.value)
|
||||
return Token(name, result_list)
|
||||
|
||||
@dataclass
|
||||
class NodeNameAndAlias:
|
||||
name: str
|
||||
alias: str
|
||||
|
||||
class DtsTransformer(Transformer):
|
||||
# Flatten the start node into a list
|
||||
def start(self, tokens):
|
||||
@ -20,17 +26,20 @@ class DtsTransformer(Transformer):
|
||||
raise Exception(f"Unsupported DTS version: {version}")
|
||||
return DtsVersion(version)
|
||||
def device(self, tokens: list):
|
||||
identifier = "UNKNOWN"
|
||||
node_name = None
|
||||
node_alias = None
|
||||
properties = list()
|
||||
devices = list()
|
||||
for index, entry in enumerate(tokens):
|
||||
if index == 0:
|
||||
identifier = entry.value
|
||||
elif type(entry) is DeviceProperty:
|
||||
properties.append(entry)
|
||||
elif type(entry) is Device:
|
||||
devices.append(entry)
|
||||
return Device(identifier, properties, devices)
|
||||
child_devices = list()
|
||||
for index, item in enumerate(tokens):
|
||||
if type(item) is Token and item.type == 'NODE_NAME':
|
||||
node_name = item.value
|
||||
elif type(item) is Token and item.type == 'NODE_ALIAS':
|
||||
node_alias = item.value
|
||||
elif type(item) is DeviceProperty:
|
||||
properties.append(item)
|
||||
elif type(item) is Device:
|
||||
child_devices.append(item)
|
||||
return Device(node_name, node_alias, properties, child_devices)
|
||||
def device_property(self, objects: List[object]):
|
||||
name = objects[0]
|
||||
# Boolean property has no value as the value is implied to be true
|
||||
@ -44,13 +53,20 @@ class DtsTransformer(Transformer):
|
||||
if type(token) is Token:
|
||||
raise Exception(f"Failed to convert token to PropertyValue: {token}")
|
||||
return token
|
||||
def PHANDLE(self, token: Token):
|
||||
return PropertyValue(type="phandle", value=token.value[1:])
|
||||
def values(self, object):
|
||||
return PropertyValue(type="values", value=object)
|
||||
def value(self, object):
|
||||
# PHANDLE is already converted to PropertyValue
|
||||
if isinstance(object[0], PropertyValue):
|
||||
return object[0]
|
||||
return PropertyValue(type="value", value=object[0])
|
||||
def array(self, object):
|
||||
return PropertyValue(type="array", value=object)
|
||||
def VALUE(self, token: Token):
|
||||
if token.value.startswith("&"):
|
||||
return PropertyValue(type="phandle", value=token.value[1:])
|
||||
return token.value
|
||||
def NUMBER(self, token: Token):
|
||||
return token.value
|
||||
@ -65,3 +81,5 @@ class DtsTransformer(Transformer):
|
||||
return PropertyValue("text_array", result_list)
|
||||
def INCLUDE_C(self, token: Token):
|
||||
return IncludeC(token.value)
|
||||
def DEFINE_C(self, token: Token):
|
||||
return DefineC(token.value)
|
||||
@ -15,7 +15,7 @@
|
||||
gpio-count = <49>;
|
||||
};
|
||||
|
||||
i2c_internal {
|
||||
i2c_internal: i2c0 {
|
||||
compatible = "espressif,esp32-i2c";
|
||||
port = <I2C_NUM_0>;
|
||||
clock-frequency = <400000>;
|
||||
@ -23,7 +23,7 @@
|
||||
pin-scl = <8>;
|
||||
};
|
||||
|
||||
i2c_external {
|
||||
i2c_external: i2c1 {
|
||||
compatible = "espressif,esp32-i2c";
|
||||
port = <I2C_NUM_1>;
|
||||
clock-frequency = <400000>;
|
||||
|
||||
@ -23,7 +23,8 @@
|
||||
pin-scl = <2>;
|
||||
};
|
||||
|
||||
// ES8311 (also connected to I2C bus)
|
||||
// ES8311
|
||||
// TODO: init via I2C to enable audio playback
|
||||
i2s0 {
|
||||
compatible = "espressif,esp32-i2s";
|
||||
port = <I2S_NUM_0>;
|
||||
|
||||
64
devicetree.c
Normal file
64
devicetree.c
Normal file
@ -0,0 +1,64 @@
|
||||
// Default headers
|
||||
#include <tactility/device.h>
|
||||
// DTS headers
|
||||
#include <tactility/bindings/root.h>
|
||||
#include <tactility/bindings/esp32_gpio.h>
|
||||
#include <tactility/bindings/esp32_i2c.h>
|
||||
#include <tactility/bindings/esp32_i2s.h>
|
||||
|
||||
static const root_config_dt root_config = {
|
||||
"LilyGO T-Deck"
|
||||
};
|
||||
|
||||
static struct Device root = {
|
||||
.name = "/",
|
||||
.config = &root_config,
|
||||
.parent = NULL,
|
||||
};
|
||||
|
||||
static const esp32_i2s_config_dt i2s0_config = {
|
||||
I2S_NUM_0,
|
||||
7,
|
||||
5,
|
||||
6,
|
||||
GPIO_PIN_NONE,
|
||||
GPIO_PIN_NONE
|
||||
};
|
||||
|
||||
static struct Device i2s0 = {
|
||||
.name = "i2s0",
|
||||
.config = &i2s0_config,
|
||||
.parent = &root,
|
||||
};
|
||||
|
||||
static const esp32_i2c_config_dt i2c0_config = {
|
||||
I2C_NUM_0,
|
||||
400000,
|
||||
18,
|
||||
8,
|
||||
0,
|
||||
0
|
||||
};
|
||||
|
||||
static struct Device i2c0 = {
|
||||
.name = "i2c0",
|
||||
.config = &i2c0_config,
|
||||
.parent = &root,
|
||||
};
|
||||
|
||||
static const esp32_i2c_config_dt i2c1_config = {
|
||||
I2C_NUM_1,
|
||||
400000,
|
||||
43,
|
||||
44,
|
||||
0,
|
||||
0
|
||||
};
|
||||
|
||||
static struct Device i2c1 = {
|
||||
.name = "i2c1",
|
||||
.config = &i2c1_config,
|
||||
.parent = &root,
|
||||
};
|
||||
|
||||
static const esp32_gpio_config_dt gpio0_config = {
|
||||
Loading…
x
Reference in New Issue
Block a user