Tab5 power expander driver and devicetree parsing improvements (#507)

* **New Features**
  * PI4IOE5V6408 I2C I/O expander driver with public GPIO APIs
  * CLI tool to list devicetree dependencies

* **Device Tree Updates**
  * M5Stack Tab5 configured with two I2C IO expanders; PI4IOE5V6408 binding added

* **Build / Tooling**
  * Devicetree code generation integrated into build; generated artifacts and dynamic dependency resolution exposed

* **Refactor**
  * Kernel/run APIs updated to accept a null‑terminated devicetree modules array; many module symbols renamed

* **Documentation**
  * Added README and Apache‑2.0 license for new driver module
This commit is contained in:
Ken Van Hoeylandt
2026-02-17 22:59:30 +01:00
committed by GitHub
parent f0f764baff
commit d2048e01b6
82 changed files with 749 additions and 253 deletions
@@ -0,0 +1,35 @@
import sys
import os
from source.printing import print_error
from source.config import parse_config
def print_help():
print("Usage: python dependencies.py [path]\n")
print("\t[in_file] the path where the root devicetree.yaml file is")
if __name__ == "__main__":
if "--help" in sys.argv:
print_help()
sys.exit()
args = [a for a in sys.argv[1:] if not a.startswith("--")]
if len(args) < 1:
print_error("Missing argument")
print_help()
sys.exit(1)
yaml_directory = args[0]
if not os.path.exists(yaml_directory):
print_error(f"Path not found: {yaml_directory}")
sys.exit(1)
config = parse_config(yaml_directory, os.getcwd())
# Device module is added first because it's started first:
# It creates the root device, so it must exist before its children.
device_dependency = os.path.basename(os.path.normpath(yaml_directory))
print(device_dependency)
for dependency in config.dependencies:
dependency_name = os.path.basename(os.path.normpath(dependency))
print(dependency_name)
@@ -212,7 +212,7 @@ def gather_devices(device: Device, output: list[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):
def generate_devicetree_c(filename: str, items: list[object], bindings: list[Binding], config, 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()
@@ -225,6 +225,7 @@ def generate_devicetree_c(filename: str, items: list[object], bindings: list[Bin
// Default headers
#include <tactility/device.h>
#include <tactility/dts.h>
#include <tactility/module.h>
// DTS headers
'''))
@@ -246,6 +247,25 @@ def generate_devicetree_c(filename: str, items: list[object], bindings: list[Bin
write_device_list_entry(file, item, bindings, verbose)
file.write("\tDTS_DEVICE_TERMINATOR\n")
file.write("};\n")
# Gather module symbols
module_symbol_names = []
for dependency in config.dependencies:
dependency_name = os.path.basename(os.path.normpath(dependency))
module_symbol_name = f"{dependency_name.replace('-', '_')}"
if not module_symbol_name.endswith("_module"):
module_symbol_name += "_module"
module_symbol_names.append(module_symbol_name)
file.write("\n")
# Forward declaration of symbol variables
for symbol in module_symbol_names:
file.write(f"extern struct Module {symbol};\n")
file.write("\n")
# Create array of symbol variables
file.write("struct Module* dts_modules[] = {\n")
for symbol in module_symbol_names:
file.write(f"\t&{symbol},\n")
file.write("\tNULL\n")
file.write("};\n")
def generate_devicetree_h(filename: str):
with open(filename, "w") as file:
@@ -258,17 +278,21 @@ def generate_devicetree_h(filename: str):
extern "C" {
#endif
// Array of device tree modules terminated with DTS_MODULE_TERMINATOR
extern struct DtsDevice dts_devices[];
// Array of module symbols terminated with NULL
extern struct Module* dts_modules[];
#ifdef __cplusplus
}
#endif
'''))
def generate(output_path: str, items: list[object], bindings: list[Binding], verbose: bool):
def generate(output_path: str, items: list[object], bindings: list[Binding], config, verbose: bool):
if not os.path.exists(output_path):
os.makedirs(output_path)
devicetree_c_filename = os.path.join(output_path, "devicetree.c")
generate_devicetree_c(devicetree_c_filename, items, bindings, verbose)
generate_devicetree_c(devicetree_c_filename, items, bindings, config, verbose)
devicetree_h_filename = os.path.join(output_path, "devicetree.h")
generate_devicetree_h(devicetree_h_filename)
@@ -46,7 +46,7 @@ def main(config_path: str, output_path: str, verbose: bool) -> int:
if verbose:
for binding in bindings:
pprint(binding)
generate(output_path, transformed, bindings, verbose)
generate(output_path, transformed, bindings, config, verbose)
return 0
except DevicetreeException as caught:
print("\033[31mError: ", caught, "\033[0m")