From fbb3ee341e462e66f1b31af65e8e45a94fd4819f Mon Sep 17 00:00:00 2001 From: Phil Elwell Date: Thu, 28 May 2026 12:16:17 +0100 Subject: [PATCH] Add the dtapply script and examples dtapply is a script that reads a config.txt and uses dtmerge to apply all its dtparam and dtoverlay directives to a base .dtb file, honouring any model-specific conditional filters. Signed-off-by: Phil Elwell --- CMakeLists.txt | 1 + README.md | 2 + dtapply/CMakeLists.txt | 7 + dtapply/README.md | 184 +++++++++++ dtapply/dtapply | 340 ++++++++++++++++++++ dtapply/examples/config-basic.txt | 20 ++ dtapply/examples/config-overlay-context.txt | 54 ++++ dtapply/examples/config-sections.txt | 46 +++ 8 files changed, 654 insertions(+) create mode 100644 dtapply/CMakeLists.txt create mode 100644 dtapply/README.md create mode 100755 dtapply/dtapply create mode 100644 dtapply/examples/config-basic.txt create mode 100644 dtapply/examples/config-overlay-context.txt create mode 100644 dtapply/examples/config-sections.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index b22956c..90f56de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ project(utils) option(ENABLE_WERROR "Treat compiler warnings as errors" OFF) # List of subsidiary CMakeLists +add_subdirectory(dtapply) add_subdirectory(dtmerge) add_subdirectory(eeptools) add_subdirectory(kdtc) diff --git a/README.md b/README.md index 2f948ff..46c9ea4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # utils A collection of scripts and simple applications +* [dtapply](dtapply/) - A script that reads a Raspberry Pi `config.txt` and uses `dtmerge` + to apply all its `dtparam` and `dtoverlay` directives to a base `.dtb` file. * [dtmerge](dtmerge/) - A tool for applying compiled DT overlays (`*.dtbo`) to base Device Tree files (`*.dtb`). Also includes the `dtoverlay` and `dtparam` utilities. * [eeptools](eeptools/) - Tools for creating and managing EEPROMs for HAT+ and HAT board. diff --git a/dtapply/CMakeLists.txt b/dtapply/CMakeLists.txt new file mode 100644 index 0000000..cb3df4f --- /dev/null +++ b/dtapply/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.10...3.27) + +include(GNUInstallDirs) + +project(dtapply) + +install(PROGRAMS dtapply DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/dtapply/README.md b/dtapply/README.md new file mode 100644 index 0000000..ac5899e --- /dev/null +++ b/dtapply/README.md @@ -0,0 +1,184 @@ +# dtapply and examples + +`dtapply` reads a Raspberry Pi `config.txt` file and uses `dtmerge` to apply +all of its `dtparam` and `dtoverlay` directives to a base `.dtb` file, +producing a merged `.dtb` without requiring a running Pi. This could be used +to speed up the booting process. + +## Usage + +``` +usage: dtapply [-h] [-o OUTPUT] [--overlays-dir OVERLAYS_DIR] + [--dtmerge DTMERGE] [--model MODEL] [-d] [-n] + dtb config + +positional arguments: + dtb Base DTB file (e.g. bcm2711-rpi-4-b.dtb) + config Raspberry Pi config.txt file + +options: + -h, --help show this help message and exit + -o OUTPUT, --output OUTPUT + Output DTB file (default: output.dtb) + --overlays-dir OVERLAYS_DIR + Directory containing .dtbo overlay files (default: + auto-detect /boot/firmware/overlays or /boot/overlays) + --dtmerge DTMERGE Path to the dtmerge binary (default: dtmerge) + --model MODEL Override Pi model identifier for section filtering + (e.g. pi4, cm4, pi5) + -d, --debug Pass -d to dtmerge and print extra information + -n, --dry-run Parse and print directives but do not run dtmerge +``` + +## Example files + +| File | Demonstrates | +|---|---| +| `config-basic.txt` | Common `dtparam` and `dtoverlay` lines with no section filtering | +| `config-sections.txt` | Conditional `[section]` headers to target specific Pi models | +| `config-overlay-context.txt` | Multi-line overlay parameters and mixed global/overlay params | + +--- + +## config-basic.txt — Basic usage + +``` +dtapply bcm2711-rpi-4-b.dtb config-basic.txt -o merged.dtb +``` + +The simplest case: every directive falls under the implicit `[all]` section +and is applied unconditionally. `dtapply` calls `dtmerge` once per +`dtparam` block and once per `dtoverlay`, chaining the output of each step +into the next. + +``` +dtparam=audio=on +dtparam=i2c_arm=on +dtparam=spi=on +dtoverlay=vc4-kms-v3d +dtoverlay=w1-gpio,gpiopin=4 +``` + +Produces five `dtmerge` calls in sequence: + +``` +dtmerge base.dtb step1.dtb - audio=on +dtmerge step1.dtb step2.dtb - i2c_arm=on +dtmerge step2.dtb step3.dtb - spi=on +dtmerge step3.dtb step4.dtb vc4-kms-v3d.dtbo +dtmerge step4.dtb merged.dtb w1-gpio.dtbo gpiopin=4 +``` + +--- + +## config-sections.txt — Conditional section filters + +`config.txt` uses `[section]` headers to gate directives to specific +hardware. `dtapply` infers the Pi model from the DTB filename: + +| DTB prefix | Model identifier(s) | +|---|---| +| `bcm2708-rpi-zero-w` | `pi0w` | +| `bcm2708-rpi-b-plus` | `pi1` | +| `bcm2709-rpi-2-b` | `pi0w` | +| `bcm2710-rpi-zero-2-w` | `pi02` | +| `bcm2710-rpi-3-b` | `pi3` | +| `bcm2710-rpi-cm0` | `cm0` | +| `bcm2711-rpi-4-b` | `pi4` | +| `bcm2711-rpi-400` | `pi400` | +| `bcm2711-rpi-cm4` | `cm4` | +| `bcm2712-rpi-5-b` | `pi5` | +| `bcm2712-rpi-cm5` | `cm5` | +| `bcm2712-rpi-500` | `pi500` | +| … | … | +(not an exhaustive list) + +A directive is included if and only if its enclosing section matches the +inferred model, or is `[all]`. Lines before the first `[section]` header +are treated as `[all]`. `[none]` suppresses everything inside it. + +``` +dtapply bcm2711-rpi-4-b.dtb config-sections.txt -o merged-pi4.dtb +dtapply bcm2712-rpi-5-b.dtb config-sections.txt -o merged-pi5.dtb +``` + +For `bcm2711-rpi-4-b.dtb` (`pi4` model), only the `[all]` and `[pi4]` +sections are active — the `[pi5]` and `[cm4]` directives are skipped. + +If the model cannot be reliably inferred from the filename, use `--model`: + +``` +dtapply mystery-board.dtb config-sections.txt --model pi4 -o merged.dtb +``` + +Use `--dry-run` (`-n`) to inspect which directives would be applied without +actually running `dtmerge`: + +``` +dtapply bcm2712-rpi-5-b.dtb config-sections.txt -n +``` + +``` +Detected model: pi5 +Found 3 action(s) to apply: + dtparam=audio=on + dtparam=i2c_arm=on + dtoverlay=vc4-kms-v3d-pi5 + dtparam=spi=on +``` + +--- + +## config-overlay-context.txt — Overlay context and multi-line parameters + +### Parameters split across lines + +A `dtoverlay` line sets the *current overlay context*. All subsequent +`dtparam` lines are treated as additional parameters for that overlay until +the context changes. This lets you spread a long parameter list across +multiple lines: + +``` +dtoverlay=i2c-gpio +dtparam=i2c_gpio_sda=2 +dtparam=i2c_gpio_scl=3 +dtparam=i2c_gpio_delay_us=2 +dtparam=bus=3 +``` + +All five lines are collapsed into a single `dtmerge` call: + +``` +dtmerge current.dtb next.dtb i2c-gpio.dtbo \ + i2c_gpio_sda=2 i2c_gpio_scl=3 i2c_gpio_delay_us=2 bus=3 +``` + +### Global parameters inside an overlay block + +Each parameter is resolved against the current overlay's `__overrides__` +node first, falling back to the base device tree's `__overrides__` if it +is not found there. This means base-DT parameters can appear inside an +overlay block without causing an error: + +``` +dtoverlay=uart0,txd0_pin=14,rxd0_pin=15 +dtparam=i2c_arm_baudrate=400000 +``` + +Here `i2c_arm_baudrate` is a base-DT parameter, not a `uart0` parameter. +`dtmerge` detects this automatically and applies it to the base DT. The +result is the same as if the `dtparam` line had appeared outside the +overlay block. + +### Resetting the overlay context + +A bare `dtoverlay=` (nothing after the `=`) or `dtoverlay=none` ends the +current overlay context. Any `dtparam` lines that follow are then +applied to the base DT only, with no overlay lookup: + +``` +dtoverlay=gpio-fan,gpiopin=14 +dtoverlay= # end gpio-fan context + +dtparam=audio=on # base-DT parameter, no overlay involved +``` diff --git a/dtapply/dtapply b/dtapply/dtapply new file mode 100755 index 0000000..434ab41 --- /dev/null +++ b/dtapply/dtapply @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 +""" +Apply dtparam and dtoverlay directives from a Raspberry Pi config.txt to a base .dtb file, +producing a merged .dtb using the dtmerge utility. +""" + +import argparse +import os +import re +import shutil +import subprocess +import sys +import tempfile + + +# Maps section names from config.txt to model identifier strings. +# The values are sets of model IDs to which that section applies. +_SECTION_MAP = { + 'all': None, # sentinel: applies to everything + 'none': (), # applies to nothing + 'pi5': ('pi5','pi500','cm5',), + 'pi500': ('pi5',), + 'cm5': ('cm5',), + 'pi4': ('pi4','pi400','cm4',), + 'pi400': ('pi400',), + 'cm4': ('cm4',), + 'cm4s': ('cm4s',), + 'pi3': ('pi3','pi3+',), + 'pi3+': ('pi3+',), + 'cm0': {'cm0',}, + 'pi2': ('pi2',), + 'pi1': ('pi1',), + 'pi0': ('pi0','pi0w','pi02',), + 'pi0w': ('pi0w','pi02',), + 'pi02': ('pi02',), +} + + +def model_from_dtb(dtb_path: str) -> str | None: + """Infer a Pi model identifier from a DTB filename.""" + name = os.path.basename(dtb_path).lower().removesuffix('.dtb') + + if 'bcm2712' in name: + if 'cm5' in name: + return 'cm5' + if '500' in name: + return 'pi500' + return 'pi5' + + if 'bcm2711' in name: + if 'cm4s' in name: + return 'cm4s' + if 'cm4' in name: + return 'cm4' + if '400' in name: + return 'pi400' + return 'pi4' + + if 'bcm2710' in name or 'bcm2837' in name: + if '2-b' in name: + return 'pi2' + if 'cm0' in name: + return 'cm0' + if 'zero-2' in name: + return 'pi02' + if '3-b-plus' in name or '3-a-plus' in name: + return 'pi3+' + # There is no [cm3] filter + return 'pi3' + + if 'bcm2709' in name or 'bcm2836' in name: + # There is no [cm2] filter + return 'pi2' + + if 'bcm2708' in name or 'bcm2835' in name: + if 'zero-w' in name: + return 'pi0w' + if 'zero' in name: + return 'pi0' + # There is no [cm1] filter + return 'pi1' + + return None + + +def section_applies(section: str, model: str | None) -> bool: + """Return True if a config.txt conditional section applies to *model*.""" + if section not in _SECTION_MAP: + # Unknown section (e.g. [board-type=…], [EDID=…]) — skip conservatively + return False + + targets = _SECTION_MAP[section] + if targets is None: # [all] + return True + if not targets: # [none] + return False + if model is None: + # Model unknown; include everything to avoid silently dropping directives + return True + return model in targets + + +def parse_config(config_path: str, model: str | None) -> list[tuple]: + """ + Parse *config_path* and return a list of actions: + + ('base_params', ['p=v', ...]) + Apply params to the base DT (dtmerge … - p=v …). + + ('overlay', name, ['p=v', ...]) + Load overlay and apply all accumulated params (dtmerge … name.dtbo p=v …). + dtmerge resolves each param against the overlay's __overrides__ first, + then the base, so global params mixed into an overlay line are handled + correctly. + + The firmware model followed here is: + - dtparam lines are applied in the context of the most-recently-seen + dtoverlay; each param is resolved against that overlay before the base. + - A bare 'dtoverlay=' or 'dtoverlay=none' ends the current overlay context. + - dtparam with no preceding dtoverlay is a plain base-only operation. + """ + actions: list[tuple] = [] + active = True # [all] is implicitly assumed before the first section header + + # Overlay accumulator: we delay emitting an overlay action until we know + # all its params (which may arrive on subsequent dtparam lines). + pending_overlay: str | None = None + pending_params: list[str] = [] + + def flush_overlay() -> None: + nonlocal pending_overlay, pending_params + if pending_overlay is not None: + actions.append(('overlay', pending_overlay, pending_params)) + pending_overlay = None + pending_params = [] + + with open(config_path) as fh: + for raw in fh: + line = raw.strip() + + # Strip inline comments + line = re.sub(r'\s*#.*$', '', line) + if not line: + continue + + # Section header + m = re.fullmatch(r'\[([^\]]+)\]', line) + if m: + active = section_applies(m.group(1).lower(), model) + continue + + if not active: + continue + + # dtparam / dtoverlay + m = re.match(r'^(dtparam|dtoverlay)\s*=\s*(.*)', line, re.IGNORECASE) + if not m: + continue + + kind = m.group(1).lower() + rest = m.group(2).strip() + + # Split "name,p1=v1,p2=v2" into tokens. Commas separate tokens; + # quoted values with embedded commas are not supported by the + # firmware either, so this matches real-world usage. + tokens = [t.strip() for t in rest.split(',') if t.strip()] + + if kind == 'dtparam': + if not tokens: + continue + if pending_overlay is None: + # No active overlay: apply to base only. + actions.append(('base_params', tokens)) + else: + # Extend the current overlay's param list; dtmerge will + # resolve each param against the overlay then the base. + pending_params.extend(tokens) + + else: # dtoverlay + overlay_name = tokens[0] if tokens else '' + + if not overlay_name or overlay_name == 'none': + # Bare 'dtoverlay=' or 'dtoverlay=none': end current context. + flush_overlay() + + elif overlay_name == 'base': + print(" Warning: dtoverlay=base (reset) is not supported; ignoring") + flush_overlay() + + else: + # New overlay: flush any pending one first, then start accumulating. + flush_overlay() + pending_overlay = overlay_name + pending_params = list(tokens[1:]) + + flush_overlay() + return actions + + +def find_overlay(name: str, overlays_dir: str) -> str: + """Return the path to *name*.dtbo, or raise FileNotFoundError.""" + path = os.path.join(overlays_dir, name + '.dtbo') + if not os.path.isfile(path): + raise FileNotFoundError(f"Overlay not found: {path}") + return path + + +def run_dtmerge(dtmerge_bin: str, base: str, output: str, + overlay: str, params: list[str], debug: bool) -> None: + """Run dtmerge to produce *output* from *base* + *overlay* + *params*.""" + cmd = [dtmerge_bin] + if debug: + cmd.append('-d') + cmd += [base, output, overlay] + params + + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode != 0: + sys.stderr.write(result.stderr) + raise RuntimeError( + f"dtmerge failed (exit {result.returncode}): {' '.join(cmd)}" + ) + if debug and result.stderr: + sys.stderr.write(result.stderr) + + +def default_overlays_dir() -> str: + for candidate in ('/boot/firmware/overlays', '/boot/overlays'): + if os.path.isdir(candidate): + return candidate + return '/boot/firmware/overlays' + + +def dtapply(base_dtb: str, config_path: str, output_dtb: str, + overlays_dir: str, dtmerge_bin: str, + model_override: str | None, debug: bool, dry_run: bool) -> None: + + model = model_override or model_from_dtb(base_dtb) + if model: + print(f"Detected model: {model}") + else: + print("Warning: could not detect model from DTB name; all sections will be included") + + directives = parse_config(config_path, model) + if not directives: + print("No dtparam/dtoverlay directives found — copying base DTB unchanged") + shutil.copy2(base_dtb, output_dtb) + return + + print(f"Found {len(directives)} action(s) to apply:") + for action in directives: + if action[0] == 'base_params': + print(f" dtparam={','.join(action[1])}") + else: + _, name, params = action + print(f" dtoverlay={','.join([name] + params)}") + + if dry_run: + return + + with tempfile.TemporaryDirectory(prefix='dtmerge_') as tmpdir: + # Chain: each step reads from 'current' and writes to 'next' + current = base_dtb + step = 0 + + for action in directives: + step += 1 + next_dtb = os.path.join(tmpdir, f"step{step:03d}.dtb") + + if action[0] == 'base_params': + _, params = action + if debug: + print(f" dtmerge: base params {params}") + run_dtmerge(dtmerge_bin, current, next_dtb, '-', params, debug) + + else: # overlay + _, overlay_name, params = action + try: + overlay_path = find_overlay(overlay_name, overlays_dir) + except FileNotFoundError as exc: + sys.exit(f"Error: {exc}") + if debug: + print(f" dtmerge: dtoverlay={overlay_name} params={params}") + run_dtmerge(dtmerge_bin, current, next_dtb, overlay_path, params, debug) + + current = next_dtb + + shutil.copy2(current, output_dtb) + + print(f"Written: {output_dtb}") + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Apply config.txt dtparam/dtoverlay directives to a base .dtb file " + "using the dtmerge utility." + ) + parser.add_argument('dtb', help="Base DTB file (e.g. bcm2711-rpi-4-b.dtb)") + parser.add_argument('config', help="Raspberry Pi config.txt file") + parser.add_argument('-o', '--output', default='output.dtb', + help="Output DTB file (default: output.dtb)") + parser.add_argument('--overlays-dir', default=None, + help="Directory containing .dtbo overlay files " + "(default: auto-detect /boot/firmware/overlays or /boot/overlays)") + parser.add_argument('--dtmerge', default='dtmerge', + help="Path to the dtmerge binary (default: dtmerge)") + parser.add_argument('--model', default=None, + help="Override Pi model identifier for section filtering " + "(e.g. pi4, cm4, pi5)") + parser.add_argument('-d', '--debug', action='store_true', + help="Pass -d to dtmerge and print extra information") + parser.add_argument('-n', '--dry-run', action='store_true', + help="Parse and print directives but do not run dtmerge") + args = parser.parse_args() + + if not os.path.isfile(args.dtb): + sys.exit(f"Error: base DTB not found: {args.dtb}") + if not os.path.isfile(args.config): + sys.exit(f"Error: config.txt not found: {args.config}") + + overlays_dir = args.overlays_dir or default_overlays_dir() + + if not args.dry_run and not os.path.isdir(overlays_dir): + sys.exit(f"Error: overlays directory not found: {overlays_dir}") + + if shutil.which(args.dtmerge) is None and not os.path.isfile(args.dtmerge): + sys.exit(f"Error: dtmerge binary not found: {args.dtmerge}") + + dtapply( + base_dtb=args.dtb, + config_path=args.config, + output_dtb=args.output, + overlays_dir=overlays_dir, + dtmerge_bin=args.dtmerge, + model_override=args.model, + debug=args.debug, + dry_run=args.dry_run, + ) + +if __name__ == '__main__': + main() diff --git a/dtapply/examples/config-basic.txt b/dtapply/examples/config-basic.txt new file mode 100644 index 0000000..9998e47 --- /dev/null +++ b/dtapply/examples/config-basic.txt @@ -0,0 +1,20 @@ +# Basic config.txt example +# +# Demonstrates the most common dtparam and dtoverlay usage. +# All lines here fall under the implicit [all] section, so they apply +# to every Pi model. + +# Enable the audio output pin +dtparam=audio=on + +# Enable I2C on the ARM core (accessible as /dev/i2c-1) +dtparam=i2c_arm=on + +# Enable SPI +dtparam=spi=on + +# Load the VC4 KMS graphics overlay +dtoverlay=vc4-kms-v3d + +# Load a 1-Wire bus on GPIO 4 +dtoverlay=w1-gpio,gpiopin=4 diff --git a/dtapply/examples/config-overlay-context.txt b/dtapply/examples/config-overlay-context.txt new file mode 100644 index 0000000..b1f885d --- /dev/null +++ b/dtapply/examples/config-overlay-context.txt @@ -0,0 +1,54 @@ +# Overlay-context config.txt example +# +# Demonstrates two related features of config.txt parameter processing: +# +# 1. Splitting overlay parameters across multiple dtparam lines. +# 2. Using global (base-DT) parameters in the middle of an overlay block. +# +# The firmware (and dtapply) track a "current overlay". Every parameter +# encountered while an overlay is active is resolved against that overlay's +# __overrides__ node first; only if it is not found there is it applied to +# the base device tree instead. This means a single dtoverlay block can +# set both overlay-specific and global parameters without needing a +# separate dtparam= line outside the block. +# +# A bare "dtoverlay=" (nothing after the =) ends the current overlay +# context, returning to base-only mode. "dtoverlay=none" has the same +# effect. + +# ----- Example 1: parameters split across lines ------------------------- +# +# Instead of crowding everything onto one line: +# dtoverlay=i2c-gpio,i2c_gpio_sda=2,i2c_gpio_scl=3,i2c_gpio_delay_us=2,bus=3 +# +# ... the parameters can be spread across dtparam lines for readability. +# All four lines below are passed together in a single dtmerge call. + +dtoverlay=i2c-gpio +dtparam=i2c_gpio_sda=2 +dtparam=i2c_gpio_scl=3 +dtparam=i2c_gpio_delay_us=2 +dtparam=bus=3 + +# ----- Example 2: global parameter inline with an overlay --------------- +# +# 'i2c_arm_baudrate' is a parameter of the base device tree, not of the +# uart0 overlay. Placing it here is valid: dtmerge will see that it is +# not in uart0's __overrides__ and will apply it to the base DT instead. +# The net result is identical to a separate dtparam=i2c_arm_baudrate=400000 +# line, but keeps logically related config together. + +dtoverlay=uart0,txd0_pin=14,rxd0_pin=15 +dtparam=i2c_arm_baudrate=400000 # global param, resolved against base DT + +# ----- Example 3: resetting the overlay context ------------------------- +# +# After "dtoverlay=", subsequent dtparam lines apply to the base DT only. +# This is useful when you need to set a base parameter that shares a name +# with a parameter in the preceding overlay and you want to be certain it +# goes to the base. + +dtoverlay=gpio-fan,gpiopin=14 +dtoverlay= # end gpio-fan context + +dtparam=audio=on # unambiguously a base-DT parameter diff --git a/dtapply/examples/config-sections.txt b/dtapply/examples/config-sections.txt new file mode 100644 index 0000000..07b384a --- /dev/null +++ b/dtapply/examples/config-sections.txt @@ -0,0 +1,46 @@ +# Conditional-sections config.txt example +# +# Demonstrates how [section] headers gate directives to specific Pi models. +# dtapply infers the model from the DTB filename (e.g. bcm2711-rpi-4-b.dtb +# → pi4) and only applies directives from matching sections. +# Use --model to override the inferred model. + +# Lines before the first section header are always applied (equivalent to [all]). +dtparam=audio=on + +# ----------------------------------------------------------------------- +# [all] applies to every model, just like lines before the first header. +# ----------------------------------------------------------------------- +[all] +dtparam=i2c_arm=on + +# ----------------------------------------------------------------------- +# Pi 4 and Pi 400 each have their own section. The Pi 400 is electrically +# identical to the Pi 4 for DT purposes, but has its own DTB, so it gets +# its own section here. +# ----------------------------------------------------------------------- +[pi4] +dtoverlay=vc4-kms-v3d,cma-512 + +[pi400] +dtoverlay=vc4-kms-v3d,cma-512 + +# ----------------------------------------------------------------------- +# Pi 5 uses a different KMS overlay. +# ----------------------------------------------------------------------- +[pi5] +dtoverlay=vc4-kms-v3d-pi5 + +# ----------------------------------------------------------------------- +# Compute Module 4: no wireless, may need explicit antenna or SDIO config. +# ----------------------------------------------------------------------- +[cm4] +dtoverlay=vc4-kms-v3d,cma-256 +dtparam=ant2 # select external antenna + +# ----------------------------------------------------------------------- +# Back to [all] for directives that should apply to every model regardless +# of what came before. +# ----------------------------------------------------------------------- +[all] +dtparam=spi=on