From 5a8069a2105d85308789bc6d4965a651063360aa Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Sun, 11 Oct 2026 18:56:35 -0700 Subject: [PATCH] zephyr-cp: keep board buses that have fixed pins The iobroker check treated a bus as routable at runtime when every nRF psel in its default pinctrl state was disconnected. Pinctrl states on other SoCs have no psels, so the list was empty, all() returned True, and every I2C, SPI and UART on RP2, SiWx917 and other non-nRF boards was dropped from board. busio then refused pins too. Only treat a bus as routable when it has psels and all are disconnected. Move the check into a helper so it can be tested. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../zephyr-cp/cptools/tests/test_zephyr2cp.py | 48 +++++++++++++++++++ ports/zephyr-cp/cptools/zephyr2cp.py | 17 +++++-- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py b/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py index 556aec0cccb..403bd2fd94e 100644 --- a/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py +++ b/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py @@ -24,6 +24,7 @@ MINIMUM_RAM_SIZE, add_toml_pin_names, _toml_status_pin, + _dynamically_routable, ) @@ -796,3 +797,50 @@ def test_absent_key_is_none(self): def test_unknown_name_raises(self): with pytest.raises(RuntimeError, match="STATUS_NEOPIXEL = 'NOPE' is not a board pin name"): _toml_status_pin({"STATUS_NEOPIXEL": "NOPE"}, self.BOARD_NAMES, "STATUS_NEOPIXEL") + + +class TestDynamicallyRoutable: + """Test suite for _dynamically_routable function.""" + + def _bus(self, group): + dts = f""" +/dts-v1/; + +/ {{ + pinctrl {{ + bus_default: bus_default {{ + group1 {{ + {group} + }}; + }}; + }}; + + bus: bus@1000 {{ + reg = <0x1000 0x100>; + pinctrl-0 = <&bus_default>; + pinctrl-names = "default"; + }}; +}}; +""" + return parse_dts_string(dts).label2node["bus"] + + def test_nrf_all_disconnected(self): + assert _dynamically_routable(self._bus("psels = <0x0b0001ff 0x0c0001ff>;")) + + def test_nrf_connected(self): + assert not _dynamically_routable(self._bus("psels = <0x0b00000a 0x0c0001ff>;")) + + def test_pinmux_without_psels(self): + assert not _dynamically_routable(self._bus("pinmux = <0x1234 0x5678>;")) + + def test_no_pinctrl(self): + dts = """ +/dts-v1/; + +/ { + bus: bus@1000 { + reg = <0x1000 0x100>; + }; +}; +""" + assert not _dynamically_routable(parse_dts_string(dts).label2node["bus"]) diff --git a/ports/zephyr-cp/cptools/zephyr2cp.py b/ports/zephyr-cp/cptools/zephyr2cp.py index 660b263262f..ee813ba4883 100644 --- a/ports/zephyr-cp/cptools/zephyr2cp.py +++ b/ports/zephyr-cp/cptools/zephyr2cp.py @@ -550,6 +550,17 @@ def _pinctrl_default_psels(node): return psels +def _dynamically_routable(node): + """Return True when every nRF psel in the node's default pinctrl state is + disconnected. Pinctrl states on other SoCs have no psels, so their buses + keep the pins the devicetree gives them. + """ + psels = _pinctrl_default_psels(node) + return bool(psels) and all( + (value & NRF_PIN_FIELD_MASK) == NRF_PIN_FIELD_MASK for value in psels + ) + + def _populate_input_key_names(): header = ( pathlib.Path(__file__).parent.parent @@ -1300,11 +1311,7 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig iobroker_labels = set() for driver in BUSIO_CLASSES: for labels in active_zephyr_devices.get(driver, []): - node = device_tree.label2node[labels[0]] - psels = _pinctrl_default_psels(node) - if psels is not None and all( - (value & NRF_PIN_FIELD_MASK) == NRF_PIN_FIELD_MASK for value in psels - ): + if _dynamically_routable(device_tree.label2node[labels[0]]): iobroker_labels.add(labels[0]) zephyr_binding_headers = []