diff --git a/Buildscripts/DevicetreeCompiler/source/generator.py b/Buildscripts/DevicetreeCompiler/source/generator.py index 795077f09..8a85e1fcc 100644 --- a/Buildscripts/DevicetreeCompiler/source/generator.py +++ b/Buildscripts/DevicetreeCompiler/source/generator.py @@ -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 diff --git a/Buildscripts/DevicetreeCompiler/source/grammar.lark b/Buildscripts/DevicetreeCompiler/source/grammar.lark index 0b498ab64..e2a0493ff 100644 --- a/Buildscripts/DevicetreeCompiler/source/grammar.lark +++ b/Buildscripts/DevicetreeCompiler/source/grammar.lark @@ -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+ diff --git a/Buildscripts/DevicetreeCompiler/source/models.py b/Buildscripts/DevicetreeCompiler/source/models.py index b747869f3..a865c698f 100644 --- a/Buildscripts/DevicetreeCompiler/source/models.py +++ b/Buildscripts/DevicetreeCompiler/source/models.py @@ -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 diff --git a/Buildscripts/DevicetreeCompiler/source/transformer.py b/Buildscripts/DevicetreeCompiler/source/transformer.py index 13183769f..bb70e3cde 100644 --- a/Buildscripts/DevicetreeCompiler/source/transformer.py +++ b/Buildscripts/DevicetreeCompiler/source/transformer.py @@ -3,6 +3,7 @@ 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 @@ def dts_version(self, tokens: List[Token]): 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 @@ def property_value(self, tokens: List): 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 @@ -64,4 +80,6 @@ def quoted_text_array(self, tokens: List[Token]): result_list.append(token.value) return PropertyValue("text_array", result_list) def INCLUDE_C(self, token: Token): - return IncludeC(token.value) \ No newline at end of file + return IncludeC(token.value) + def DEFINE_C(self, token: Token): + return DefineC(token.value) \ No newline at end of file diff --git a/Devices/lilygo-tdeck/lilygo,tdeck.dts b/Devices/lilygo-tdeck/lilygo,tdeck.dts index 655c1bdcf..559af7ec0 100644 --- a/Devices/lilygo-tdeck/lilygo,tdeck.dts +++ b/Devices/lilygo-tdeck/lilygo,tdeck.dts @@ -15,7 +15,7 @@ gpio-count = <49>; }; - i2c_internal { + i2c_internal: i2c0 { compatible = "espressif,esp32-i2c"; port = ; clock-frequency = <400000>; @@ -23,7 +23,7 @@ pin-scl = <8>; }; - i2c_external { + i2c_external: i2c1 { compatible = "espressif,esp32-i2c"; port = ; clock-frequency = <400000>; diff --git a/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts b/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts index 7ed66e9fb..92d56abd7 100644 --- a/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts +++ b/Devices/lilygo-tlora-pager/lilygo,tlora-pager.dts @@ -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 = ; diff --git a/devicetree.c b/devicetree.c new file mode 100644 index 000000000..c675bd3fe --- /dev/null +++ b/devicetree.c @@ -0,0 +1,64 @@ +// Default headers +#include +// DTS headers +#include +#include +#include +#include + +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 = {