Generated banner for Jetson Customize Pinmux
Generated logo for Jetson Customize Pinmux

Jetson Customize Pinmux

NVIDIAOfficialCreated by Jetson TeamDistributed by NVIDIA

  • Skill
  • Tool integration
  • Cloud and DevOps
  • Apache-2.0
  • Medium risk
  • Has scripts

From the author

Per-pin SFIO / direction / initial-state configurator for a Jetson Orin or Thor custom carrier from the pinmux XLSM. Do NOT use for kernel-DT overlay or ODMDATA edits.
— the description by Jetson Team

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The jetson-customize-pinmux skill provides a structured workflow for configuring per-pin SFIO, direction, and initial state for Jetson Orin or Thor custom carriers. It parses the official Tegra pinmux spreadsheet (XLSM) to generate the necessary BCT DTSI files (pinmux, gpio, and padvoltage). The skill ensures that pin configurations are accurately captured, validated, and integrated into the carrier's bootloader configuration, following a strict interactive process to prevent hardware misconfiguration.

What it does

  • Parses Tegra pinmux XLSM files to establish a ground truth for SoC pin configurations.
  • Provides an interactive lookup tool to identify pins by ball name, signal, or DT name.
  • Guides users through a mandatory Q1-Q6 interactive loop to set pin attributes.
  • Generates pinmux, gpio, and padvoltage BCT DTSI files for custom carrier boards.
  • Updates carrier configuration references to include the newly generated DTSI files.
  • Ensures configuration idempotency by using specific markers in the generated files.

When to use it

  • When you need to repurpose a CVM ball (e.g., switching between GPIO and a peripheral function).
  • After deriving a custom carrier and needing to apply specific pinmux settings from an XLSM.
  • When a pin mismatch is reported by other Jetson customization skills.
  • When you need to define initial pin states, drive types, or pull attributes for custom hardware.

Advantages

  • Ensures consistency by using the official XLSM as the single source of truth.
  • Prevents configuration errors through a mandatory, guided per-pin interactive process.
  • Automates the complex generation of three distinct BCT DTSI files.
  • Integrates cleanly with existing Jetson BSP workflows and overlay trackers.

Usage examples

“Configure pin H10 on my custom carrier to act as a GPIO.”
The agent will look up pin H10, confirm it is GPIO-capable, and prompt you through the required direction and initial state settings before generating the updated DTSI files.
“I need to set the drive type for the PCIE4_RST_L signal.”
The agent will identify the corresponding DT pin, check if the pad is configurable, and if so, guide you through the Q4-Q6 attributes including drive type.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/nvidia/jetson-customize-pinmux/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip jetson-customize-pinmux.zip -d .agents/skills

Documentation by Jetson Team

Customize pinmux (per-pin SFIO / direction / state)

Overview

The Tegra pinmux spreadsheet (.xlsm) is the ground truth for every CVM ball: SoC pin name, supported SFIOs, customer-selected function, direction, and initial state. This skill parses that XLSM, runs a per-pin Q1–Q6 interactive loop, and emits the three BCT DTSIs (pinmux, gpio, padvoltage) in one shot into the overlay tracker at <source.root_path>/Linux_for_Tegra/bootloader/.

Unlike sibling skills jetson-customize-uphy / jetson-customize-pcie / jetson-customize-camera, pinmux has no kernel-DT overlay surface and no ODMDATA edit. The XLSM is the source of truth; the three emitted DTSIs land at flash time via the carrier conf's PINMUX_CONFIG= / GPIOINT_CONFIG= / PMC_CONFIG= references (which /jetson-derive-carrier set up).

Pad classification (silicon-fixed): only BD* and BI* pads have configurable pull / drive / open-drain attributes. LP5XA_*, UPHYDS_*, DP_SINGLE_*, BDMIPI16X_*, BDUSB2_*, OSCI27_* are fixed-function and skip Q4–Q6 (configurable: no).

The bundled scripts/modify_pinmux.py is the workhorse: it parses the XLSM via openpyxl>=3.1, builds the per-carrier pinmap JSON, captures pin edits into a session shim, and (on generate) writes the three DTSIs.

When to invoke

  • The user says "configure pin", "set SFIO", "edit pinmux DTSI", "set pin direction", "set initial state", or asks to repurpose a CVM ball (e.g. flip a pin between GPIO and a peripheral function).

  • A sibling skill (jetson-customize-camera, jetson-customize-pcie, jetson-customize-usb, jetson-customize-mgbe) reports an HSIO pin mismatch via pin_verifier.py and the user wants to fix it.

  • The user pre-derived a custom carrier with /jetson-derive-carrier and now wants to author the pinmux from a freshly-edited .xlsm.

Prerequisites:

  • Active profile selected (target-platform/active_target.yml → <profile>.yaml with reference_devkit: AND custom_carrier:).

  • <source.root_path>/Linux_for_Tegra/ exists as a git repo (/jetson-init-source).

  • /jetson-derive-carrier has run — the three pinmux-side BCT DTSIs (PINMUX_CONFIG, GPIOINT_CONFIG, PMC_CONFIG references in the carrier conf) exist in the overlay tracker.

  • A pinmux .xlsm is registered in the active profile at documents.custom_carrier_pinmux_xls (preferred when custom-carrier- specific) or documents.ref_devkit_pinmux_xls (fallback). The bundled modify_pinmux.py requires openpyxl>=3.1.

Procedure

See references/procedure.md for the full step-by-step procedure (Steps 1–8). Summary:

  1. Resolve active target + XLSM. Validate active profile, custom_carrier:, overlay-tracker prerequisites; resolve the pinmux .xlsm path from documents.custom_carrier_pinmux_xls → documents.ref_devkit_pinmux_xls → single XLSM under documents.root_path → user prompt.

  2. Probe. Run modify_pinmux.py probe to parse the XLSM into the per-skill scratch <KB>/pinmap/<custom-carrier>.json plus session.json shim.

  3. Lookup. Resolve a free-form user query (CVM ball, Verilog name, signal, DT pin) via modify_pinmux.py lookup; surface supported SFIO list, defaults, and configurable: yes/no.

  4. Set-pin (HARD GATE — Q1–Q6 via AskUserQuestion). Q1–Q3 (sfio / direction / initial_state) always asked; Q4–Q6 (pull / drive_type / open_drain) only when configurable: yes. tristate and e_input are derived from direction, never asked.

  5. Generate. modify_pinmux.py generate --out-dir <source.root_path>/Linux_for_Tegra/bootloader/ (root, not bootloader/generic/BCT/ — derive-carrier .dts forks live there, do not colocate). Emits: tegra<soc>-mb1-bct-{pinmux,gpio,padvoltage}-<carrier-key>.dtsi. <carrier-key> comes from the carrier conf's PINMUX_CONFIG= reference, NOT the kebab-cased carrier name.

  6. Commit (single batched commit per workflow rule). All three DTSIs are one logical edit → one customization commit. Run the commit-preview gate before each commit.

  7. Run-state sidecar + session shim. Write the user-facing <profile-stem>.jetson-customize-pinmux.json sidecar and the transient session.json shim under <workspace>/target-platform/.

  8. Summary. Emit the standard one-line + table summary.

Gotchas

  • No kernel-DT overlay; no OVERLAY_DTB_FILE edit; no render_conf.py hand-off. This skill ends at the three BCT DTSIs — the carrier conf already references them via PINMUX_CONFIG= / GPIOINT_CONFIG= / PMC_CONFIG= (set up by /jetson-derive-carrier).

  • Re-point the .dts wrapper's #include after generate. /jetson-derive-carrier forks the .dts wrappers at bootloader/generic/BCT/, but their #include lines may still pull the upstream devkit .dtsi (e.g. …-p3834-xxxx-p4071-0000.dtsi). After generate writes the new <CARRIER_KEY>.dtsi to bootloader/ root, edit each wrapper's #include to the new filename — by bare basename (#include "tegra<soc>-mb1-bct-pinmux-<CARRIER_KEY>.dtsi"), not ../../… filesystem-relative. The BCT build's cpp -I bootloader/ resolves bare basenames; that's the convention every other BCT include in the tree follows. Roll the wrapper edits into the same customization commit as the three DTSIs. See references/procedure.md Step 5 ("Sanity-check the carrier .dts wrapper").

  • Q4–Q6 gated on configurable: yes. Asking pull / drive_type / open_drain on a fixed-function pad (LP5XA_*, UPHYDS_*, BDMIPI16X_*, etc.) is silently dropped by the script and confuses the user. lookup prints configurable: yes/no — always check it before prompting Q4–Q6.

  • tristate and e_input are derived, never asked. unused → tristate=ENABLE; input / bidirectional → enable-input=ENABLE. Exposing them as separate prompts produces inconsistent DTSIs.

  • sfio=gpio requires a parseable gpio=GPIOn_PD.NN entry in the pinmap row's sfio list. Pins without one are GPIO-incapable silicon; set-pin rejects the call. Surface the rejection — don't silently fall back to a non-GPIO SFIO.

  • Marker idempotency. Every per-pin edit carries // custom-bsp: pinmux on the closing brace; gpio default-state entries carry the same marker as a trailing comment. Re-running generate must detect and update — never duplicate.

  • modify_pinmux.py is unchanged from the original framework — it reads its own session.json shim under --kb-dir. The shim is regenerated each run from the active profile + the user-facing sidecar. Do not hand-edit the shim; it's transient.

  • Multiple pinmux DTSI variants per Thor module SKU. Some carrier pins live in a different DTSI variant than the one the carrier conf references. modify_pinmux.py commit (legacy patch-in-place flow) tolerates missing per-pin blocks via pinmux.warnings[] rather than failing. Surface the warning; point at the alternate DTSI variant.

  • Don't touch the upstream BSP at <bsp_image.root_path>. All edits land in <source.root_path>/Linux_for_Tegra/bootloader/ under the pristine + customization commit pattern.

Available Scripts

ScriptPurposeArguments
scripts/modify_pinmux.pyXLSM parser + per-pin DTSI generator. Invoked via run_script() from Steps 3-6 with the subcommand of the current phase.probe | lookup | set-pin | apply | generate | commit [...] (see --help)
scripts/generate_dtsi.pyRenders pinmux/GPIO/padvoltage DTSI fragments from the bundled session state. Called by modify_pinmux.py generate.--session <path> --out-dir <dir>

Invoke from the skill body as a subprocess via run_script():

# run_script: probe the carrier pinmux XLSM and write a session state
scripts/modify_pinmux.py probe --xlsm carrier.xlsm --session .pinmux-session.json

# run_script: render DTSI fragments from the final session state
scripts/modify_pinmux.py generate --session .pinmux-session.json --out-dir bsp_sources/pinmux/

References

  • references/procedure.md — full Step 1–8 procedure prose.

  • questions.json — Q1–Q6 prompt schema consumed by Step 4.

  • ../../references/platform_template.yaml — documents: block (ref_devkit_pinmux_xls, custom_carrier_pinmux_xls).

  • ../../context/bsp-customization-workflow.md — overlay edit protocol (single-commit per DTSI fork).

  • ../jetson-derive-carrier/SKILL.md — must run first; produces the pinmux / gpio / padvoltage DTSI forks this skill edits, and rewrites the carrier conf's PINMUX_CONFIG= / GPIOINT_CONFIG= / PMC_CONFIG= lines to point at them.

  • ../jetson-init-source/SKILL.md — produces the overlay tracker this skill commits into.

  • ../jetson-link-docs/SKILL.md — author the profile's documents: block, including the pinmux XLSM bindings.

Files

scripts/generate_dtsi.py

#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Generate pinmux / gpio / padvoltage DTSIs from a Jetson Thor pinmux XLSM.

Replicates the "Customer Generate DT" Excel macro on Linux without requiring
Excel or LibreOffice. Reads the XLSM with openpyxl (no VBA execution) and
emits three DTSI files that are byte-for-byte compatible with the macro output.

Usage::

    python3 generate_dtsi.py \\
        --xlsm  /path/to/Jetson_Thor_Series_Modules_Pinmux_Template_v1.7.xlsm \\
        --out-dir /path/to/Linux_for_Tegra/bootloader \\
        --carrier-name p3834-xxxx-p4071-0000   # suffix used in filenames

Output (all in --out-dir):
    tegra264-mb1-bct-pinmux-<carrier-name>.dtsi
    tegra264-mb1-bct-gpio-<carrier-name>.dtsi
    tegra264-mb1-bct-padvoltage-<carrier-name>.dtsi

Verified against Thor r38.4 devkit DTSIs generated by
T264_Pinmux_Config_CVM_Jedha_P4070.xlsm Revision 9.
"""

from __future__ import annotations

import argparse
import copy
import pathlib
import re
import sys
from collections import defaultdict
from dataclasses import dataclass, field

try:
    import openpyxl
except ImportError:
    openpyxl = None

# ---------------------------------------------------------------------------
# Column map (1-based) — Thor v1.7 template, Revision 39
# Validated against row 7 headers; see _detect_columns().
# ---------------------------------------------------------------------------
THOR_COLS: dict[str, int] = {
    "connector_pin":  1,
    "signal_name":    2,
    "package_ball":   3,   # "MPIO" in header row
    "verilog_name":   4,
    "mux_unused":     5,
    "mux_gpio":       6,
    "mux_sfio0":      7,
    "mux_sfio1":      8,
    "mux_sfio2":      9,
    "mux_sfio3":      10,
    "dir_gpio":       11,
    "dir_sfio0":      12,
    "dir_sfio1":      13,
    "dir_sfio2":      14,
    "dir_sfio3":      15,
    "dt_pin_name":    21,
    "power_rail":     22,
    "pad_category":   28,
    "pad_type":       29,
    "pull_strength":  30,
    "por_state":      31,
    "pin_group":      32,
    "pupd":           34,
    "tristate":       35,
    "e_input":        36,
    "gpio_init_val":  37,   # AK — GPIO Init Value: z / 0 / 1
    "customer_usage": 45,
    "pin_direction":  46,
    "e_io_od":        50,   # AX — Open-Drain Select
    "drv_type":       51,   # AY — DRV_TYPE: Disable=1X, Enable=High(2X)
    "e_lpbk":         52,   # AZ — Loopback Select
    "io_voltage":     61,   # BI — IO Block Voltage e.g. "1.8V" / "3.3V"
}

_NON_DATA_SHEETS = {
    "customer-readme", "quick guide", "revision history",
    "revision history internal only", "template",
}

# ---------------------------------------------------------------------------
# GPIO bank → controller address (Tegra264 / Thor)
# Source: bootloader/tegra264-gpio.h
# ---------------------------------------------------------------------------
_MAIN_GPIO_BANKS = {
    "F", "G", "H", "J", "K", "L", "M", "P", "Q", "R",
    "S", "T", "U", "V", "W", "X", "Y", "Z", "AL",
}
_UPHY_GPIO_BANKS = {"A", "B", "C", "D", "E"}
_AON_GPIO_BANKS = {"AA", "BB", "CC", "DD", "EE"}
_FSI_GPIO_BANKS = {"AB", "AC", "AD", "AE", "AF", "AG", "AH", "AJ"}

_BANK_TO_CTRL: dict[str, str] = {}
for _b in _MAIN_GPIO_BANKS:
    _BANK_TO_CTRL[_b] = "gpio@ac300000"
for _b in _UPHY_GPIO_BANKS:
    _BANK_TO_CTRL[_b] = "gpio@e8300000"
for _b in _AON_GPIO_BANKS:
    _BANK_TO_CTRL[_b] = "gpio@8cf00000"
for _b in _FSI_GPIO_BANKS:
    _BANK_TO_CTRL[_b] = "gpio@b0320000"

_CTRL_TO_MACRO: dict[str, str] = {
    "gpio@ac300000": "TEGRA264_MAIN_GPIO",
    "gpio@e8300000": "TEGRA264_UPHY_GPIO",
    "gpio@8cf00000": "TEGRA264_AON_GPIO",
    "gpio@b0320000": "TEGRA264_FSI_GPIO",
}

# Ordered for deterministic output (matches devkit reference DTSI order)
_CTRL_ORDER = ["gpio@ac300000", "gpio@e8300000", "gpio@8cf00000", "gpio@b0320000"]

# ---------------------------------------------------------------------------
# Pad-voltage voltage string → DTS macro
# ---------------------------------------------------------------------------
_VOLTAGE_MACRO: dict[str, str] = {
    "3.3v": "IO_PAD_VOLTAGE_3_3V",
    "3.3":  "IO_PAD_VOLTAGE_3_3V",
    "1.8v": "IO_PAD_VOLTAGE_1_8V",
    "1.8":  "IO_PAD_VOLTAGE_1_8V",
    "1.2v": "IO_PAD_VOLTAGE_1_2V",
    "1.2":  "IO_PAD_VOLTAGE_1_2V",
}


# ---------------------------------------------------------------------------
# Data model
# ---------------------------------------------------------------------------
@dataclass
class _PinIds:
    """Pin identity fields (names, ball, signal)."""
    dt_pin_name: str
    verilog_name: str
    package_ball: str
    connector_pin: str
    signal_name: str
    power_rail: str
    customer_usage: str


@dataclass
class _PinElec:
    """Electrical/configuration fields from the XLSM."""
    pin_direction: str
    gpio_init_val: str
    pupd: str
    tristate: str
    e_input: str
    io_voltage: str
    por_state: str


@dataclass
class _PinDrive:
    """Drive-related electrical fields (open-drain, drive type, loopback)."""
    e_io_od: str
    drv_type: str
    e_lpbk: str


@dataclass
class _PinDerived:
    """Derived fields populated during parsing."""
    sfio_options: list[str] = field(default_factory=list)
    resolved_func: str = ""
    pad_type: str = ""
    pin_group: str = ""


_GROUP_NAMES = ("ids", "elec", "drive", "derived")


@dataclass
class PinRow:
    """One pin's parsed XLSM row, used for DTSI emission + pinmap export."""
    ids: _PinIds
    elec: _PinElec
    drive: _PinDrive
    derived: _PinDerived

    def __getattr__(self, name: str):
        for grp_name in _GROUP_NAMES:
            grp = self.__dict__.get(grp_name)
            if grp is not None and hasattr(grp, name):
                return getattr(grp, name)
        raise AttributeError(name)

    def __setattr__(self, name: str, value) -> None:
        if name in _GROUP_NAMES:
            object.__setattr__(self, name, value)
            return
        for grp_name in _GROUP_NAMES:
            grp = self.__dict__.get(grp_name)
            if grp is not None and hasattr(grp, name):
                setattr(grp, name, value)
                return
        object.__setattr__(self, name, value)


# ---------------------------------------------------------------------------
# XLSM parsing
# ---------------------------------------------------------------------------

def _s(v) -> str:
    """Cell value → stripped string."""
    return "" if v is None else str(v).strip()


def _find_header_row(ws) -> int:
    for r in range(1, min(ws.max_row, 25) + 1):
        if _s(ws.cell(r, 1).value) == "Pin #":
            return r
    return -1


def _detect_data_sheet(wb) -> tuple:
    """Return (worksheet, header_row) for the first data sheet found."""
    for name in wb.sheetnames:
        if name.lower().strip() in _NON_DATA_SHEETS:
            continue
        ws = wb[name]
        hr = _find_header_row(ws)
        if hr != -1:
            return ws, hr
    raise SystemExit("generate_dtsi: no data sheet found in workbook")


def _resolve_func(row_vals: list, cols: dict[str, int]) -> tuple[str, list[str]]:
    """Determine nvidia,function from customer_usage vs mux columns.

    Returns (function_string, sfio_options_list).
    function_string is lowercased DT function name (e.g. 'i2c7_clk', 'gpio').
    """
    cu = _s(row_vals[cols["customer_usage"] - 1]).lower()
    mux_keys = ["mux_gpio", "mux_sfio0", "mux_sfio1", "mux_sfio2", "mux_sfio3"]
    sfio_opts: list[str] = []
    func = ""

    for k in mux_keys:
        raw = _s(row_vals[cols[k] - 1])
        if not raw:
            continue
        lowered = raw.lower()
        # Normalise GPIO entries: "GPIO3_PG.01" → "gpio"
        if re.match(r"gpio\d*_", lowered) or lowered.startswith("gpio"):
            token = "gpio"
        else:
            token = lowered
        if token not in sfio_opts:
            sfio_opts.append(token)
        # Match customer_usage to this mux slot
        if not func:
            cu_norm = re.sub(r"[^a-z0-9]", "_", cu)
            tok_norm = re.sub(r"[^a-z0-9]", "_", token)
            if cu_norm == tok_norm or cu_norm in tok_norm or tok_norm in cu_norm:
                func = token

    # Pins with no mux/sfio options (only mux_unused set) → reserved function.
    if not sfio_opts:
        return "rsvd0", []

    # Fallback: if customer_usage starts with "unused" → rsvd0 (pin not used on
    # this carrier).  Mirrors the Excel VBA macro which outputs rsvd0 for all
    # "unused_*" pins even when the silicon exposes a non-reserved SFIO option.
    if not func:
        if cu.startswith("unused") or cu == "":
            func = "rsvd0"
        else:
            func = re.sub(r"[^a-z0-9_]", "_", cu).strip("_") or "gpio"

    return func, sfio_opts


def _parse_gpio_bank_index(dt_pin_name: str) -> tuple[str, int] | None:
    """Extract (BANK, index) from DT pin name suffix.

    Examples:
      gen7_i2c_scl_pg1  → ("G", 1)
      soc_gpio00_paa0   → ("AA", 0)
      hdmi_cec_paa6     → ("AA", 6)
      soc_gpio61_pal5   → ("AL", 5)
    """
    # Match trailing _p<bank><index> where bank is 1-2 letters
    m = re.search(r"_p([a-z]{1,2})(\d+)$", dt_pin_name.lower())
    if not m:
        return None
    bank = m.group(1).upper()
    idx = int(m.group(2))
    return bank, idx


@dataclass
class _ParsedRowInputs:
    """Already-computed fields passed to _build_pinrow."""
    dt_pin: str
    verilog: str
    ball: str
    pad_type: str
    sfio_opts: list[str]
    func: str


def _build_pinrow(row_vals, cols, parsed: _ParsedRowInputs) -> PinRow:
    """Construct a PinRow from a raw XLSM data row + parsed metadata."""
    return PinRow(
        ids=_PinIds(
            dt_pin_name=parsed.dt_pin,
            verilog_name=parsed.verilog,
            package_ball=parsed.ball,
            connector_pin=_s(row_vals[cols["connector_pin"] - 1]),
            signal_name=_s(row_vals[cols["signal_name"] - 1]),
            power_rail=_s(row_vals[cols["power_rail"] - 1]).lower(),
            customer_usage=_s(row_vals[cols["customer_usage"] - 1]),
        ),
        elec=_PinElec(
            pin_direction=_s(row_vals[cols["pin_direction"] - 1]),
            gpio_init_val=_s(row_vals[cols["gpio_init_val"] - 1]).lower(),
            pupd=_s(row_vals[cols["pupd"] - 1]),
            tristate=_s(row_vals[cols["tristate"] - 1]),
            e_input=_s(row_vals[cols["e_input"] - 1]),
            io_voltage=_s(row_vals[cols["io_voltage"] - 1]),
            por_state=_s(row_vals[cols["por_state"] - 1]),
        ),
        drive=_PinDrive(
            e_io_od=_s(row_vals[cols["e_io_od"] - 1]),
            drv_type=_s(row_vals[cols["drv_type"] - 1]),
            e_lpbk=_s(row_vals[cols["e_lpbk"] - 1]),
        ),
        derived=_PinDerived(
            sfio_options=parsed.sfio_opts,
            resolved_func=parsed.func,
            pad_type=parsed.pad_type,
            pin_group=_s(row_vals[cols["pin_group"] - 1]),
        ),
    )


def _parse_one_row(row_vals: list, cols: dict[str, int]) -> PinRow | None:
    """Process a single raw XLSM data row. Returns None if the row is skipped."""
    ball = _s(row_vals[cols["package_ball"] - 1])
    verilog = _s(row_vals[cols["verilog_name"] - 1])
    if not ball or not verilog:
        return None

    pad_type = _s(row_vals[cols["pad_type"] - 1])
    is_mpio = pad_type.startswith(_MPIO_PAD_PREFIXES)

    has_mux = any(
        _s(row_vals[cols[k] - 1])
        for k in ("mux_gpio", "mux_sfio0", "mux_sfio1", "mux_sfio2", "mux_sfio3")
    )
    has_mux_unused = bool(_s(row_vals[cols["mux_unused"] - 1]))
    if not has_mux and not (is_mpio and has_mux_unused):
        return None

    dt_pin = _s(row_vals[cols["dt_pin_name"] - 1]).lower().replace(" ", "_")
    if not dt_pin:
        dt_pin = verilog.lower().replace(" ", "_")

    func, sfio_opts = _resolve_func(row_vals, cols)
    parsed = _ParsedRowInputs(
        dt_pin=dt_pin, verilog=verilog, ball=ball,
        pad_type=pad_type, sfio_opts=sfio_opts, func=func,
    )
    return _build_pinrow(row_vals, cols, parsed)


def parse_xlsm(xlsm_path: pathlib.Path) -> list[PinRow]:
    """Parse all data rows from the XLSM; return list of PinRow."""
    if openpyxl is None:
        raise ImportError("openpyxl is required to parse XLSM files")
    wb = openpyxl.load_workbook(str(xlsm_path), data_only=True, keep_vba=False)
    ws, header_row = _detect_data_sheet(wb)
    cols = THOR_COLS
    max_col = max(cols.values()) + 2

    rows: list[PinRow] = []
    for r in range(header_row + 1, ws.max_row + 1):
        row_vals = [ws.cell(r, c).value for c in range(1, max_col)]
        pin = _parse_one_row(row_vals, cols)
        if pin is not None:
            rows.append(pin)

    wb.close()
    print(f"parsed {len(rows)} pin rows from {xlsm_path.name}", file=sys.stderr)
    return rows


# ---------------------------------------------------------------------------
# DTSI generation helpers
# ---------------------------------------------------------------------------

_SPDX_HEADER = (
    "/*\n"
    " * SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION"
    " & AFFILIATES. All rights reserved.\n"
) + """\
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
"""


def _tegra_pin_val(raw: str, enable_val: str = "TEGRA_PIN_ENABLE",
                   disable_val: str = "TEGRA_PIN_DISABLE") -> str:
    """Map 'Enable'/'Disable'/'' → DTS macro string."""
    r = raw.strip().lower()
    if r in ("enable", "1", "yes", "true"):
        return enable_val
    if r in ("disable", "0", "no", "false"):
        return disable_val
    # Default safe fallback
    return disable_val


def _pull_val(pupd: str) -> str:
    r = pupd.strip().lower()
    if "up" in r:
        return "TEGRA_PIN_PULL_UP"
    if "down" in r:
        return "TEGRA_PIN_PULL_DOWN"
    return "TEGRA_PIN_PULL_NONE"


def _drv_type_val(drv_type: str) -> str:
    # AY column values:
    #   "Enable" / "High"     → TEGRA_PIN_2X_DRIVER  (BDGPIO high-drive)
    #   "DEF_1X"              → TEGRA_PIN_DEFAULT_DRIVE_1X  (BDSDMEM pads)
    #   "Disable" / ""        → TEGRA_PIN_1X_DRIVER  (BDGPIO normal)
    r = drv_type.strip().lower()
    if r in ("enable", "1", "high"):
        return "TEGRA_PIN_2X_DRIVER"
    if r in ("def_1x", "default_1x", "default"):
        return "TEGRA_PIN_DEFAULT_DRIVE_1X"
    return "TEGRA_PIN_1X_DRIVER"


def _tristate_for_dir(pin: PinRow) -> str:
    """Tristate is ENABLE for Unused pins or when explicitly set.

    The XLSM tristate column (col 35) carries 'TRISTATE' or 'NORMAL' — map
    those to ENABLE / DISABLE respectively.
    """
    d = pin.pin_direction.strip().lower()
    if d == "unused":
        return "TEGRA_PIN_ENABLE"
    # Use the sheet's tristate column if populated
    ts = pin.tristate.strip().upper()
    if ts == "TRISTATE":
        return "TEGRA_PIN_ENABLE"
    # "NORMAL" → DISABLE (also the fallback for empty)
    return "TEGRA_PIN_DISABLE"


def _einput_for_dir(pin: PinRow) -> str:
    """Enable-input: ENABLE for Input/Bidirectional, DISABLE for Output/Unused."""
    d = pin.pin_direction.strip().lower()
    if d in ("input", "bidirectional", "bidir"):
        return "TEGRA_PIN_ENABLE"
    if pin.e_input:
        return _tegra_pin_val(pin.e_input)
    return "TEGRA_PIN_DISABLE"


# ---------------------------------------------------------------------------
# Pinmux DTSI
# ---------------------------------------------------------------------------

_MPIO_PAD_PREFIXES = ("BDGPIO", "BDSDMEM", "BDDPAUX")


def generate_pinmux_dtsi(pins: list[PinRow], carrier_name: str,
                         xlsm_name: str) -> str:
    """Render the pinmux DTSI for the given parsed pins and carrier name."""
    lines: list[str] = []
    lines.append(f"/*This dtsi file was generated by generate_dtsi.py from {xlsm_name} */")
    lines.append(_SPDX_HEADER)
    lines.append("")
    lines.append('#include "t264-pinctrl-tegra.h"')
    lines.append("")
    lines.append(f'#include "./tegra264-mb1-bct-gpio-{carrier_name}.dtsi"')
    lines.append("")
    lines.append("pinmux@ac281000 {")
    lines.append("\t\tcommon {")
    lines.append("\t\t\t/* SFIO Pin Configuration */")

    # Only emit MPIO pads (BDGPIO*, BDSDMEM*, BDDPAUX*) that have a pin_group
    # (col 32) — same filter as the Excel VBA macro.  Pins without a pin_group
    # are module-internal special pads (JTAG_TDO, PMU_INT_N, SOC_PWR_REQ,
    # SDMMC1_COMP) that the VBA macro does not emit.
    mpio_pins = [p for p in pins
                 if p.pad_type.startswith(_MPIO_PAD_PREFIXES) and p.pin_group]

    for pin in mpio_pins:
        is_sdmem = pin.pad_type.startswith("BDSDMEM")
        lines.append(f"\t\t\t{pin.dt_pin_name} {{")
        lines.append(f'\t\t\t\tnvidia,pins = "{pin.dt_pin_name}";')
        lines.append(f'\t\t\t\tnvidia,function = "{pin.resolved_func}";')
        lines.append(f"\t\t\t\tnvidia,pull = <{_pull_val(pin.pupd)}>;")
        lines.append(f"\t\t\t\tnvidia,tristate = <{_tristate_for_dir(pin)}>;")
        lines.append(f"\t\t\t\tnvidia,enable-input = <{_einput_for_dir(pin)}>;")
        lines.append(f"\t\t\t\tnvidia,drv-type = <{_drv_type_val(pin.drv_type)}>;")
        if not is_sdmem:
            # BDSDMEM pads have no e-io-od property in the reference DTSI
            lines.append(f"\t\t\t\tnvidia,e-io-od = <{_tegra_pin_val(pin.e_io_od)}>;")
        lines.append(f"\t\t\t\tnvidia,e-lpbk = <{_tegra_pin_val(pin.e_lpbk)}>;")
        lines.append("\t\t\t};")
        lines.append("")

    lines.append("\t\t};")
    lines.append("};")
    lines.append("")
    return "\n".join(lines)


# ---------------------------------------------------------------------------
# GPIO DTSI
# ---------------------------------------------------------------------------

def _collect_gpio_buckets(pins: list[PinRow]) -> tuple[dict, dict, dict]:
    """Bucket gpio pins by controller and direction (input/out-low/out-high)."""
    ctrl_input: dict[str, list[str]] = defaultdict(list)
    ctrl_outlow: dict[str, list[str]] = defaultdict(list)
    ctrl_outhigh: dict[str, list[str]] = defaultdict(list)
    for pin in pins:
        if pin.resolved_func != "gpio":
            continue
        parsed = _parse_gpio_bank_index(pin.dt_pin_name)
        if not parsed:
            continue
        bank, idx = parsed
        ctrl = _BANK_TO_CTRL.get(bank)
        if not ctrl:
            continue
        entry = f"\t\t\t{_CTRL_TO_MACRO[ctrl]}({bank}, {idx})"
        d = pin.pin_direction.strip().lower()
        iv = pin.gpio_init_val.strip().lower()
        if d == "input" or d in ("bidirectional", "bidir"):
            ctrl_input[ctrl].append(entry)
        elif d == "output":
            (ctrl_outhigh if iv == "1" else ctrl_outlow)[ctrl].append(entry)
    return ctrl_input, ctrl_outlow, ctrl_outhigh


def _emit_gpio_ctrl_block(ctrl: str, ctrl_input: dict, ctrl_outlow: dict,
                          ctrl_outhigh: dict) -> list[str]:
    """Emit DTSI lines for a single gpio controller's default block."""
    lines = [f"{ctrl} {{", "\t default {"]
    if ctrl_input.get(ctrl):
        lines.append("\t\tgpio-input = <")
        lines.extend(ctrl_input[ctrl])
        lines.append("\t\t\t>;")
    lines.append("\t\tgpio-output-low = <")
    lines.extend(ctrl_outlow.get(ctrl, []))
    lines.append("\t\t\t>;")
    lines.append("\t\tgpio-output-high = <")
    lines.extend(ctrl_outhigh.get(ctrl, []))
    lines.append("\t\t\t>;")
    lines.append("\t};")
    lines.append("};")
    return lines


def generate_gpio_dtsi(pins: list[PinRow], carrier_name: str,
                       xlsm_name: str) -> str:
    """Render the gpio default-state DTSI for the given parsed pins."""
    del carrier_name  # part of the public DTSI-generator signature
    ctrl_input, ctrl_outlow, ctrl_outhigh = _collect_gpio_buckets(pins)

    lines: list[str] = [
        f"/*This dtsi file was generated by generate_dtsi.py from {xlsm_name} */",
        _SPDX_HEADER,
        '#include "tegra264-gpio.h"',
        "",
    ]
    for ctrl in _CTRL_ORDER:
        if not (ctrl_input.get(ctrl) or ctrl_outlow.get(ctrl)
                or ctrl_outhigh.get(ctrl)):
            continue
        lines.extend(_emit_gpio_ctrl_block(
            ctrl, ctrl_input, ctrl_outlow, ctrl_outhigh))
    lines.append("")
    return "\n".join(lines)


# ---------------------------------------------------------------------------
# Padvoltage DTSI
# ---------------------------------------------------------------------------

def parse_pad_voltage_groups(xlsm_path: pathlib.Path) -> dict[str, str]:
    """Read pad-group voltage settings from XLSM section header rows.

    The Excel macro reads VDDIO_G / VDDIO_J / VDDIO_P from section-header rows
    (col 3 = group name, col 61 = voltage) rather than per-pin io_voltage.
    When a cell contains a compound value like "1.8V/3.3V", the last listed
    voltage is used as the default (matches reference DTSI behaviour).

    Only the three Thor PMC io-pad group names that appear in the DT binding
    are returned: vddio_g, vddio_j, vddio_p.  All other VDDIO_* section
    headers (VDDIO_A, VDDIO_B, VDDIO_C … VDDIO_DDR*) are not PMC pad nodes
    and must not appear in the padvoltage DTSI.
    """
    allowed_groups = {"VDDIO_G", "VDDIO_J", "VDDIO_P"}
    if openpyxl is None:
        raise ImportError("openpyxl is required to parse XLSM files")
    wb = openpyxl.load_workbook(str(xlsm_path), data_only=True, keep_vba=False)
    ws, _ = _detect_data_sheet(wb)
    groups: dict[str, str] = {}
    for row in ws.iter_rows(min_row=1, values_only=True):
        name = _s(row[2])   # col 3
        volt = _s(row[60])  # col 61
        if not name or not volt:
            continue
        name_upper = name.strip().upper()
        if name_upper not in allowed_groups:
            continue
        for opt in reversed(volt.split("/")):
            macro = _VOLTAGE_MACRO.get(opt.strip().lower())
            if macro:
                groups[name_upper.lower()] = macro
                break
    wb.close()
    return groups


def generate_padvoltage_dtsi(pins: list[PinRow], carrier_name: str,
                             xlsm_name: str,
                             pad_voltage_groups: dict[str, str] | None = None
                             ) -> str:
    """Generate the padvoltage DTSI.

    When `pad_voltage_groups` is provided (from `parse_pad_voltage_groups`),
    use those group-level settings - matching how the Excel macro works.
    Falls back to per-pin io_voltage scanning when not provided.
    """
    del carrier_name  # part of the public DTSI-generator signature
    if pad_voltage_groups is not None:
        rail_voltage = pad_voltage_groups
    else:
        rail_voltage = {}
        for pin in pins:
            if not pin.power_rail or not pin.io_voltage:
                continue
            rail = pin.power_rail.lower().strip()
            for opt in reversed(pin.io_voltage.split("/")):
                macro = _VOLTAGE_MACRO.get(opt.strip().lower())
                if macro and rail:
                    rail_voltage[rail] = macro
                    break

    lines: list[str] = []
    lines.append(f"/*This dtsi file was generated by generate_dtsi.py from {xlsm_name} */")
    lines.append(_SPDX_HEADER)
    lines.append("#define IO_PAD_VOLTAGE_1_2V 1200000")
    lines.append("#define IO_PAD_VOLTAGE_1_8V 1800000")
    lines.append("#define IO_PAD_VOLTAGE_3_3V 3300000")
    lines.append("")
    lines.append("")
    lines.append("pmc@8c800000 {")
    lines.append("\tio-pad-defaults {")

    for rail in sorted(rail_voltage):
        lines.append(f"\t\t{rail} {{")
        lines.append(f"\t\t\tnvidia,io-pad-init-voltage = <{rail_voltage[rail]}>;")
        lines.append("\t\t};")
        lines.append("")

    lines.append("\t};")
    lines.append("};")
    lines.append("")
    return "\n".join(lines)


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

_DIR_TITLE_MAP = {
    "input": "Input", "output": "Output",
    "bidirectional": "Bidirectional", "bidir": "Bidirectional",
    "unused": "Unused",
}
_PULL_TITLE_MAP = {"none": "None", "pull-up": "Pull-Up", "pull-down": "Pull-Down"}
_DRV_TITLE_MAP = {"normal": "Disable", "high": "Enable"}


def _apply_direction_state(pin: PinRow, direction: str, init_state: str) -> None:
    """Update pin direction / enable-input / tristate from an edit."""
    if direction in _DIR_TITLE_MAP:
        pin.pin_direction = _DIR_TITLE_MAP[direction]
    if direction in ("input", "bidirectional"):
        pin.e_input = "Enable"
        pin.tristate = "Disable"
    elif direction == "output":
        pin.e_input = "Disable"
        pin.tristate = "Disable"
    elif direction == "unused":
        pin.e_input = "Disable"
        pin.tristate = "Enable"
    if init_state == "hi-z":
        pin.tristate = "Enable"


def _apply_electrical(pin: PinRow, edit: dict) -> None:
    """Apply pull / drv / open_drain / loopback overrides to a pin."""
    pull = (edit.get("pull") or "").lower()
    if pull in _PULL_TITLE_MAP:
        pin.pupd = _PULL_TITLE_MAP[pull]
    drv = (edit.get("drive_type") or "").lower()
    if drv in _DRV_TITLE_MAP:
        pin.drv_type = _DRV_TITLE_MAP[drv]
    od = (edit.get("open_drain") or "").lower()
    if od in ("enable", "disable"):
        pin.e_io_od = od.capitalize()
    lb = (edit.get("loopback") or "").lower()
    if lb in ("enable", "disable"):
        pin.e_lpbk = lb.capitalize()


def _apply_one_edit(pin: PinRow, edit: dict) -> PinRow:
    """Return a copy of `pin` with the user's edit baked in."""
    pin = copy.copy(pin)
    sfio = edit.get("sfio", pin.resolved_func)
    pin.resolved_func = sfio
    if sfio not in pin.sfio_options:
        pin.sfio_options = [sfio] + list(pin.sfio_options)
    direction = (edit.get("direction") or "").lower()
    init_state = (edit.get("initial_state") or "n/a").lower()
    if sfio == "gpio":
        pin.gpio_init_val = {"high": "1", "low": "0"}.get(init_state, "z")
    _apply_direction_state(pin, direction, init_state)
    _apply_electrical(pin, edit)
    return pin


def apply_pin_edits(pins: list[PinRow], edits: list[dict]) -> list[PinRow]:
    """Override PinRow fields with per-pin edits from session.pinmux.pin_edits."""
    edit_by_pin = {e["pin"]: e for e in edits}
    result: list[PinRow] = []
    for pin in pins:
        edit = edit_by_pin.get(pin.dt_pin_name)
        if edit:
            pin = _apply_one_edit(pin, edit)
        result.append(pin)
    return result


def build_pinmap_records(pins: list[PinRow]) -> list[dict]:
    """Convert PinRow list to pinmap-compatible dicts for modify_pinmux lookup.

    The sfio field uses the ``mode=name`` encoding that ``_sfio_options()``
    in modify_pinmux.py expects:
      - ``"gpio=gpio"``   → mode gpio  → emits ``"gpio"``
      - ``"sfio0=i2c7_clk"`` → mode sfio0 → emits ``"i2c7_clk"``

    The gpio bank/idx is NOT encoded in the sfio string here; modify_pinmux
    derives it from the DT pin name suffix (``_p<bank><idx>``), which is
    present in every Thor dt_pin_name produced by the XLSM.
    """
    records: list[dict] = []
    for pin in pins:
        # Build sfio list in mode=name format.
        sfio_entries: list[str] = []
        sfio_idx = 0
        for opt in pin.sfio_options:
            if opt == "gpio":
                sfio_entries.append("gpio=gpio")
            else:
                sfio_entries.append(f"sfio{sfio_idx}={opt}")
                sfio_idx += 1

        # Normalise direction to single-char convention used by existing
        # pinmap rows (I/O/B/U) so _print_row_details can display it.
        d = pin.pin_direction.strip().lower()
        dir_char = {"input": "I", "output": "O", "bidirectional": "B",
                    "bidir": "B", "unused": "U"}.get(d, d[:1].upper() if d else "")

        # Normalise XLSM raw strings to the values set-pin expects.
        pupd_raw = pin.pupd.strip().lower()
        pupd_norm = ("pull-up" if "up" in pupd_raw
                     else "pull-down" if "down" in pupd_raw
                     else "none")
        drv_norm = "high" if pin.drv_type.strip().lower() == "enable" else "normal"
        od_norm = "enable" if pin.e_io_od.strip().lower() == "enable" else "disable"
        lpbk_norm = "enable" if pin.e_lpbk.strip().lower() == "enable" else "disable"

        # A pad is software-configurable (pull/drv/od/lpbk editable) only when
        # its pad_type starts with "BD" or "BI" — i.e. BDGPIO*, BDDPAUX*,
        # BDSDMEM*, BIRST*, BICLK32K*, BIPOD*.  LP5XA_*, UPHYDS_*, DP_SINGLE_*,
        # BDMIPI16X_*, BDUSB2_*, OSCI27_* have empty attribute fields and are
        # fixed-function pads; per-pin questions 4-6 do not apply to them.
        configurable = pin.pad_type.upper().startswith(("BD", "BI"))

        records.append({
            "pin": pin.dt_pin_name,
            "ball": pin.package_ball,
            "connector_pin": pin.connector_pin,
            "verilog_name": pin.verilog_name,
            "signal_name": pin.signal_name,
            "customer_usage": pin.customer_usage,
            "direction": dir_char,
            "category": "other",
            "sfio": sfio_entries,
            "evidence": [],
            "power_rail": pin.power_rail,
            "pad_type": pin.pad_type,
            "por_state": pin.por_state,
            "configurable": configurable,
            "pupd": pupd_norm if configurable else "",
            "drv_type": drv_norm if configurable else "",
            "e_io_od": od_norm if configurable else "",
            "e_lpbk": lpbk_norm if configurable else "",
        })
    return records


def main(argv=None) -> int:
    """CLI entry point: parse the XLSM and write the 3 DTSI files."""
    p = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    p.add_argument("--xlsm", required=True, type=pathlib.Path,
                   help="Path to the Jetson Thor pinmux template .xlsm")
    p.add_argument("--out-dir", required=True, type=pathlib.Path,
                   help="Output directory for the three .dtsi files")
    p.add_argument("--carrier-name", required=True,
                   help="Carrier suffix for filenames, e.g. p3834-xxxx-p4071-0000")
    p.add_argument("--dry-run", action="store_true",
                   help="Parse and report counts but do not write files")
    args = p.parse_args(argv)

    xlsm = args.xlsm.expanduser().resolve()
    if not xlsm.exists():
        print(f"error: xlsm not found: {xlsm}", file=sys.stderr)
        return 1

    out_dir = args.out_dir.expanduser().resolve()
    if not args.dry_run:
        out_dir.mkdir(parents=True, exist_ok=True)

    pins = parse_xlsm(xlsm)

    gpio_pins = [p for p in pins if p.resolved_func == "gpio"]
    sfio_pins = [p for p in pins if p.resolved_func != "gpio"]
    rails = {p.power_rail for p in pins if p.power_rail and p.io_voltage}

    print(f"  total pins : {len(pins)}", file=sys.stderr)
    print(f"  gpio pins  : {len(gpio_pins)}", file=sys.stderr)
    print(f"  sfio pins  : {len(sfio_pins)}", file=sys.stderr)
    print(f"  pad rails  : {len(rails)}  ({', '.join(sorted(rails))})", file=sys.stderr)

    if args.dry_run:
        print("dry-run: no files written", file=sys.stderr)
        return 0

    cn = args.carrier_name
    xlsm_name = xlsm.name

    files = {
        out_dir / f"tegra264-mb1-bct-pinmux-{cn}.dtsi":
            generate_pinmux_dtsi(pins, cn, xlsm_name),
        out_dir / f"tegra264-mb1-bct-gpio-{cn}.dtsi":
            generate_gpio_dtsi(pins, cn, xlsm_name),
        out_dir / f"tegra264-mb1-bct-padvoltage-{cn}.dtsi":
            generate_padvoltage_dtsi(pins, cn, xlsm_name),
    }

    for path, content in files.items():
        path.write_text(content, encoding="utf-8")
        print(f"wrote {path}", file=sys.stderr)

    return 0


if __name__ == "__main__":
    sys.exit(main())

Requirements and compatibility

Written for: Claude Code

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Names Claude Code tools, Needs Python

Security

What our scanner noticed· Medium risk

  • Large base64-encoded blobs
  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Jetson Team
Publisher
NVIDIA (Official)
Distributed by
NVIDIA
Source
NVIDIA/skills · skills/jetson-customize-pinmux · commit fce3cca
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE-APACHE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Jetson Team or NVIDIA.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download jetson-customize-pinmux.zip