Creating Templates from Kernel Bindings

This guide walks through creating a new device template starting from a Linux kernel device tree binding. The tooling in scripts/ can parse bindings, extract compatible strings and properties, and generate starter template stubs — reducing the manual work to filling in device-specific details.

Overview

The workflow has seven steps:

Template creation workflow

Step 1: Parse kernel bindings

Clone the ADI Linux kernel (or point to an existing checkout):

git clone https://github.com/analogdevicesinc/linux.git /path/to/linux

Run the binding collector to discover all ADI bindings:

python3 scripts/collect_adi_bindings.py \
    --linux /path/to/linux \
    --output bindings_report.json \
    --markdown bindings_report.md

This produces a JSON report listing every ADI binding file with its compatible strings and properties.

Step 2: Audit against existing templates

Run the audit to find which bindings are already supported and which need templates:

python3 scripts/audit_adi_bindings.py \
    --linux /path/to/linux \
    --project .

The audit classifies each binding as:

  • Known — compatible string already in a template or profile

  • Partial — some compatible strings documented, others not

  • Undocumented — no template exists for this device

Step 3: Generate starter templates

Add --generate-templates to create stub files for undocumented devices:

python3 scripts/audit_adi_bindings.py \
    --linux /path/to/linux \
    --project . \
    --generate-templates

This creates starter .tmpl files in adidt/templates/xsa/ with boilerplate structure:

// SPDX-License-Identifier: GPL-2.0
// AUTOGENERATED STARTER TEMPLATE — requires manual review
// Source binding: Documentation/devicetree/bindings/iio/imu/adi,adis16495.yaml
//
// TODO:
//   - Replace placeholder nodes, clocks, GPIOs, interrupts
//   - Add board-specific wiring
//   - Review ADI properties from binding YAML

&spi_bus {
    status = "okay";
    #address-cells = <1>;
    #size-cells = <0>;

    device@0 {
        compatible = "adi,adis16495-1";
        reg = <0>;
        spi-max-frequency = <2000000>;
        /* TODO: Add clocks, interrupts, and device properties */
    };
};

Step 4: Refine the template

The generated stub is a starting point. Open the binding YAML to see what properties the driver expects:

cat /path/to/linux/Documentation/devicetree/bindings/iio/imu/adi,adis16495.yaml

A typical YAML binding looks like:

properties:
  compatible:
    enum:
      - adi,adis16495-1
      - adi,adis16495-2
      - adi,adis16495-3
      - adi,adis16497-1
  reg:
    maxItems: 1
  spi-max-frequency:
    maximum: 2000000
  spi-cpol: true
  spi-cpha: true
  interrupts:
    maxItems: 1

Convert these into a Jinja2 template with conditional properties:

             {{ label }}: {{ device }}@{{ cs }} {
                     compatible = "{{ compatible }}";
                     reg = <{{ cs }}>;
                     spi-max-frequency = <{{ spi_max_hz }}>;
{%- if spi_cpol %}
                     spi-cpol;
{%- endif %}
{%- if spi_cpha %}
                     spi-cpha;
{%- endif %}
{%- if interrupt_gpio is not none %}
                     interrupt-parent = <&{{ gpio_label }}>;
                     interrupts = <{{ interrupt_gpio }} {{ irq_type }}>;
{%- endif %}
             };

Key rules for templates:

  • Use tabs for indentation (DTS convention)

  • Use {%- if x is not none %} (not truthiness) for optional properties

  • Pre-format multi-value strings in the context builder, not in the template

  • Keep the closing }; at the same indentation as the opening label

Step 5: Write the context builder

Add a function to adidt/model/contexts.py that maps device parameters to template variables:

def build_adis16495_ctx(
    *,
    label: str = "imu0",
    device: str = "adis16495",
    compatible: str = "adi,adis16495-1",
    cs: int = 0,
    spi_max_hz: int = 2_000_000,
    spi_cpol: bool = True,
    spi_cpha: bool = True,
    gpio_label: str = "gpio",
    interrupt_gpio: int | None = None,
    irq_type: str = "IRQ_TYPE_EDGE_FALLING",
) -> dict:
    """Build context dict for ``adis16495.tmpl``."""
    return {
        "label": label,
        "device": device,
        "compatible": compatible,
        "cs": cs,
        "spi_max_hz": spi_max_hz,
        "spi_cpol": spi_cpol,
        "spi_cpha": spi_cpha,
        "gpio_label": gpio_label,
        "interrupt_gpio": interrupt_gpio,
        "irq_type": irq_type,
    }

Use keyword-only arguments (*) with sensible defaults from the binding’s default or maximum fields.

Step 6: Add a component factory

Add a factory function to adidt/model/components.py so users don’t need to know template filenames:

def adis16495(spi_bus: str = "spi0", cs: int = 0, **kwargs) -> ComponentModel:
    """ADIS16495 6-DOF IMU."""
    config = contexts.build_adis16495_ctx(cs=cs, **kwargs)
    return ComponentModel(
        role="imu",
        part="adis16495",
        template="adis16495.tmpl",
        spi_bus=spi_bus,
        spi_cs=cs,
        config=config,
    )

Step 7: Write tests

Add tests to verify the template renders correctly:

from adidt.model import BoardModel, components
from adidt.model.renderer import BoardModelRenderer

def test_adis16495_renders():
    model = BoardModel(
        name="test", platform="test",
        components=[
            components.adis16495(spi_bus="spi0", cs=0, interrupt_gpio=25),
        ],
    )
    nodes = BoardModelRenderer().render(model)
    out = nodes["converters"][0]
    assert 'compatible = "adi,adis16495-1"' in out
    assert "spi-cpol" in out
    assert "interrupts = <25" in out

Run with:

python3 -m pytest test/test_adis16495.py -v

Reference: what the tools extract

The binding parser (scripts/adi_binding_lib.py) extracts:

Field

Extracted?

Notes

Compatible strings

Yes

From YAML compatible key or TXT “Compatible:” section

Property names

Yes

adi,* prefixed properties from YAML properties section

Property types

No

YAML has type info (uint32, string, bool) but not extracted

Default values

No

YAML default fields not extracted

Required vs optional

No

YAML required list not extracted

Descriptions

No

Prose descriptions not extracted

For properties not automatically extracted, check the binding YAML directly. The properties section lists every valid property with its type, constraints, and description.

Tips

  • Start simple. A template with just compatible, reg, spi-max-frequency, and an interrupt covers most simple SPI devices. Add properties incrementally.

  • Check the driver source. The Linux driver’s of_match_table and probe function show exactly which DT properties are read. Path: drivers/iio/<subsystem>/<device>.c

  • Test with dtc. Compile your template output to verify syntax:

    dtc -@ -I dts -O dtb -o /dev/null my_overlay.dts
    
  • Use existing templates as reference. The adidt/templates/xsa/ directory has 35+ templates covering simple SPI devices to complex JESD204 transceivers.