Skip to content

Commit 4f9bbda

Browse files
committed
doc: boards: extensions: add Sphinx directive for board supported hardware
Introduce a new directive for displaying hardware features supported by a board, using information available in the devicetree. Signed-off-by: Benjamin Cabé <[email protected]>
1 parent 8458702 commit 4f9bbda

File tree

7 files changed

+376
-12
lines changed

7 files changed

+376
-12
lines changed

doc/_extensions/zephyr/domain/__init__.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
from zephyr.doxybridge import DoxygenGroupDirective
5454
from zephyr.gh_utils import gh_link_get_url
5555

56+
from .binding_types import BINDING_TYPES
57+
5658
__version__ = "0.2.0"
5759

5860

@@ -67,6 +69,16 @@
6769

6870
logger = logging.getLogger(__name__)
6971

72+
BINDING_TYPE_TO_DOCUTILS_NODE = {}
73+
for key, value in BINDING_TYPES.items():
74+
if isinstance(value, tuple):
75+
# For abbreviations with explanations
76+
abbr, explanation = value
77+
BINDING_TYPE_TO_DOCUTILS_NODE[key] = nodes.abbreviation(abbr, abbr, explanation=explanation)
78+
else:
79+
# For simple text
80+
BINDING_TYPE_TO_DOCUTILS_NODE[key] = nodes.Text(value)
81+
7082

7183
class CodeSampleNode(nodes.Element):
7284
pass
@@ -685,6 +697,7 @@ def run(self):
685697
board_node = BoardNode(id=board_name)
686698
board_node["full_name"] = board["full_name"]
687699
board_node["vendor"] = vendors.get(board["vendor"], board["vendor"])
700+
board_node["supported_features"] = board["supported_features"]
688701
board_node["archs"] = board["archs"]
689702
board_node["socs"] = board["socs"]
690703
board_node["image"] = board["image"]
@@ -716,6 +729,137 @@ def run(self):
716729
return [nodes.paragraph(text="Board catalog is only available in HTML.")]
717730

718731

732+
class BoardSupportedHardwareDirective(SphinxDirective):
733+
"""A directive for showing the supported hardware features of a board."""
734+
735+
has_content = False
736+
required_arguments = 0
737+
optional_arguments = 0
738+
739+
def run(self):
740+
env = self.env
741+
docname = env.docname
742+
743+
matcher = NodeMatcher(BoardNode)
744+
board_nodes = list(self.state.document.traverse(matcher))
745+
if not board_nodes:
746+
logger.warning(
747+
"board-supported-hw directive must be used in a board documentation page.",
748+
location=(docname, self.lineno),
749+
)
750+
return []
751+
752+
board_node = board_nodes[0]
753+
supported_features = board_node["supported_features"]
754+
result_nodes = []
755+
756+
paragraph = nodes.paragraph()
757+
paragraph += nodes.Text("The ")
758+
paragraph += nodes.literal(text=board_node["id"])
759+
paragraph += nodes.Text(" board supports the hardware features listed below.")
760+
result_nodes.append(paragraph)
761+
762+
if not env.app.config.zephyr_generate_hw_features:
763+
note = nodes.admonition()
764+
note += nodes.title(text="Note")
765+
note["classes"].append("warning")
766+
note += nodes.paragraph(
767+
text="The list of supported hardware features was not generated. Run a full "
768+
"documentation build for the required metadata to be available."
769+
)
770+
result_nodes.append(note)
771+
return result_nodes
772+
773+
# Add the note before any tables
774+
note = nodes.admonition()
775+
note += nodes.title(text="Note")
776+
note["classes"].append("note")
777+
note += nodes.paragraph(
778+
text="The tables below were automatically generated using information from the "
779+
"Devicetree. They may not be fully representative of all the hardware features "
780+
"supported by the board."
781+
)
782+
result_nodes.append(note)
783+
784+
for target, features in sorted(supported_features.items()):
785+
if not features:
786+
continue
787+
788+
target_heading = nodes.section(ids=[f"{board_node['id']}-{target}-hw-features"])
789+
heading = nodes.title()
790+
heading += nodes.literal(text=target)
791+
heading += nodes.Text(" target")
792+
target_heading += heading
793+
result_nodes.append(target_heading)
794+
795+
table = nodes.table(classes=["colwidths-given"])
796+
tgroup = nodes.tgroup(cols=3)
797+
798+
tgroup += nodes.colspec(colwidth=20, classes=["col-1"])
799+
tgroup += nodes.colspec(colwidth=50)
800+
tgroup += nodes.colspec(colwidth=30)
801+
802+
thead = nodes.thead()
803+
row = nodes.row()
804+
headers = ["Type", "Description", "Compatible"]
805+
for header in headers:
806+
row += nodes.entry("", nodes.paragraph(text=header))
807+
thead += row
808+
tgroup += thead
809+
810+
tbody = nodes.tbody()
811+
812+
def feature_sort_key(feature):
813+
# Put "CPU" first. Later updates might also give priority to features
814+
# like "sensor"s, for example.
815+
if feature == "cpu":
816+
return (0, feature)
817+
return (1, feature)
818+
819+
sorted_features = sorted(features.keys(), key=feature_sort_key)
820+
821+
for feature in sorted_features:
822+
items = list(features[feature].items())
823+
num_items = len(items)
824+
825+
for i, (key, value) in enumerate(items):
826+
row = nodes.row()
827+
828+
# Add type column only for first row of a feature
829+
if i == 0:
830+
type_entry = nodes.entry(morerows=num_items - 1)
831+
type_entry += nodes.paragraph(
832+
"",
833+
"",
834+
BINDING_TYPE_TO_DOCUTILS_NODE.get(
835+
feature, nodes.Text(feature)
836+
).deepcopy(),
837+
)
838+
row += type_entry
839+
840+
row += nodes.entry("", nodes.paragraph(text=value))
841+
842+
# Create compatible xref
843+
xref = addnodes.pending_xref(
844+
"",
845+
refdomain="std",
846+
reftype="dtcompatible",
847+
reftarget=key,
848+
refexplicit=False,
849+
refwarn=True,
850+
)
851+
xref += nodes.literal(text=key)
852+
row += nodes.entry("", nodes.paragraph("", "", xref))
853+
854+
tbody += row
855+
856+
tgroup += tbody
857+
table += tgroup
858+
result_nodes.append(table)
859+
860+
return result_nodes
861+
862+
719863
class ZephyrDomain(Domain):
720864
"""Zephyr domain"""
721865

@@ -734,6 +878,7 @@ class ZephyrDomain(Domain):
734878
"code-sample-category": CodeSampleCategoryDirective,
735879
"board-catalog": BoardCatalogDirective,
736880
"board": BoardDirective,
881+
"board-supported-hw": BoardSupportedHardwareDirective,
737882
}
738883

739884
object_types: dict[str, ObjType] = {
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""
2+
SPDX-FileCopyrightText: Copyright (c) 2025 The Linux Foundation
3+
SPDX-License-Identifier: Apache-2.0
4+
5+
This module contains the mapping of binding types to their human-readable names and descriptions.
6+
Each entry can be either:
7+
- A string representing the human-readable name
8+
- A tuple of (abbreviation, full name) for cases where an abbreviation exists
9+
"""
10+
11+
BINDING_TYPES = {
12+
# zephyr-keep-sorted-start re(^ ")
13+
"acpi": ("ACPI", "Advanced Configuration and Power Interface"),
14+
"adc": ("ADC", "Analog to Digital Converter"),
15+
"alh": ("ALH", "Audio Link Hub"),
16+
"arc": "ARC architecture",
17+
"arm": "ARM architecture",
18+
"audio": "Audio",
19+
"auxdisplay": "Auxiliary Display",
20+
"base": "Base",
21+
"battery": "Battery",
22+
"bluetooth": "Bluetooth",
23+
"cache": "Cache",
24+
"can": ("CAN", "Controller Area Network"),
25+
"charger": "Charger",
26+
"clock": "Clock control",
27+
"coredump": "Core dump",
28+
"counter": "Counter",
29+
"cpu": "CPU",
30+
"crypto": "Cryptographic accelerator",
31+
"dac": ("DAC", "Digital to Analog Converter"),
32+
"dai": ("DAI", "Digital Audio Interface"),
33+
"debug": "Debug",
34+
"dfpmcch": "DFPMCCH",
35+
"dfpmccu": "DFPMCCU",
36+
"disk": "Disk",
37+
"display": "Display",
38+
"display/panel": "Display panel",
39+
"dma": ("DMA", "Direct Memory Access"),
40+
"dsa": ("DSA", "Distributed Switch Architecture"),
41+
"edac": ("EDAC", "Error Detection and Correction"),
42+
"espi": ("eSPI", "Enhanced Serial Peripheral Interface"),
43+
"ethernet": "Ethernet",
44+
"firmware": "Firmware",
45+
"flash_controller": "Flash controller",
46+
"fpga": ("FPGA", "Field Programmable Gate Array"),
47+
"fs": "File system",
48+
"fuel-gauge": "Fuel gauge",
49+
"gnss": ("GNSS", "Global Navigation Satellite System"),
50+
"gpio": ("GPIO", "General Purpose Input/Output"),
51+
"haptics": "Haptics",
52+
"hda": ("HDA", "High Definition Audio"),
53+
"hdlc_rcp_if": "IEEE 802.15.4 HDLC RCP interface",
54+
"hwinfo": "Hardware information",
55+
"hwspinlock": "Hardware spinlock",
56+
"i2c": ("I2C", "Inter-Integrated Circuit"),
57+
"i2s": ("I2S", "Inter-IC Sound"),
58+
"i3c": ("I3C", "Improved Inter-Integrated Circuit"),
59+
"ieee802154": "IEEE 802.15.4",
60+
"iio": ("IIO", "Industrial I/O"),
61+
"input": "Input",
62+
"interrupt-controller": "Interrupt controller",
63+
"ipc": ("IPC", "Inter-Processor Communication"),
64+
"ipm": ("IPM", "Inter-Processor Mailbox"),
65+
"kscan": "Keyscan",
66+
"led": ("LED", "Light Emitting Diode"),
67+
"led_strip": ("LED", "Light Emitting Diode"),
68+
"lora": "LoRa",
69+
"mbox": "Mailbox",
70+
"mdio": ("MDIO", "Management Data Input/Output"),
71+
"memory-controllers": "Memory controller",
72+
"memory-window": "Memory window",
73+
"mfd": ("MFD", "Multi-Function Device"),
74+
"mhu": ("MHU", "Mailbox Handling Unit"),
75+
"net": "Networking",
76+
"mipi-dbi": ("MIPI DBI", "Mobile Industry Processor Interface Display Bus Interface"),
77+
"mipi-dsi": ("MIPI DSI", "Mobile Industry Processor Interface Display Serial Interface"),
78+
"misc": "Miscellaneous",
79+
"mm": "Memory management",
80+
"mmc": ("MMC", "MultiMediaCard"),
81+
"mmu_mpu": ("MMU / MPU", "Memory Management Unit / Memory Protection Unit"),
82+
"modem": "Modem",
83+
"mspi": "Multi-bit SPI",
84+
"mtd": ("MTD", "Memory Technology Device"),
85+
"wireless": "Wireless network",
86+
"options": "Options",
87+
"ospi": "Octal SPI",
88+
"pcie": ("PCIe", "Peripheral Component Interconnect Express"),
89+
"peci": ("PECI", "Platform Environment Control Interface"),
90+
"phy": "PHY",
91+
"pinctrl": "Pin control",
92+
"pm_cpu_ops": "Power management CPU operations",
93+
"power": "Power management",
94+
"power-domain": "Power domain",
95+
"ppc": "PPC architecture",
96+
"ps2": ("PS/2", "Personal System/2"),
97+
"pwm": ("PWM", "Pulse Width Modulation"),
98+
"qspi": "Quad SPI",
99+
"regulator": "Regulator",
100+
"reserved-memory": "Reserved memory",
101+
"reset": "Reset controller",
102+
"retained_mem": "Retained memory",
103+
"retention": "Retention",
104+
"riscv": "RISC-V architecture",
105+
"rng": ("RNG", "Random Number Generator"),
106+
"rtc": ("RTC", "Real Time Clock"),
107+
"sd": "SD",
108+
"sdhc": "SDHC",
109+
"sensor": "Sensors",
110+
"serial": "Serial controller",
111+
"shi": ("SHI", "Secure Hardware Interface"),
112+
"sip_svc": ("SIP", "Service in Platform"),
113+
"smbus": ("SMBus", "System Management Bus"),
114+
"sound": "Sound",
115+
"spi": ("SPI", "Serial Peripheral Interface"),
116+
"sram": "SRAM",
117+
"stepper": "Stepper",
118+
"syscon": "System controller",
119+
"tach": "Tachometer",
120+
"tcpc": ("TCPC", "USB Type-C Port Controller"),
121+
"test": "Test",
122+
"timer": "Timer",
123+
"timestamp": "Timestamp",
124+
"usb": "USB",
125+
"usb-c": "USB Type-C",
126+
"uac2": "USB Audio Class 2",
127+
"video": "Video",
128+
"virtualization": "Virtualization",
129+
"w1": "1-Wire",
130+
"watchdog": "Watchdog",
131+
"wifi": "Wi-Fi",
132+
"xen": "Xen",
133+
"xspi": ("XSPI", "Expanded Serial Peripheral Interface"),
134+
# zephyr-keep-sorted-stop
135+
}

doc/_extensions/zephyr/domain/templates/board-card.html

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,17 @@
1515
data-arch="{{ board.archs | join(" ") }}"
1616
data-vendor="{{ board.vendor }}"
1717
data-socs="{{ board.socs | join(" ") }}"
18-
data-supported-features="{{ board.supported_features | join(" ") }}" tabindex="0">
18+
data-supported-features="
19+
{%- set feature_types = [] -%}
20+
{%- for target_features in board.supported_features.values() -%}
21+
{%- for feature_type in target_features.keys() -%}
22+
{%- if feature_type not in feature_types -%}
23+
{%- set _ = feature_types.append(feature_type) -%}
24+
{%- endif -%}
25+
{%- endfor -%}
26+
{%- endfor -%}
27+
{{- feature_types|join(' ') -}}
28+
" tabindex="0">
1929
<div class="vendor">{{ vendors[board.vendor] }}</div>
2030
{% if board.image -%}
2131
<img alt="A picture of the {{ board.full_name }} board"

0 commit comments

Comments
 (0)