diff --git a/.codespell/ignore-words.txt b/.codespell/ignore-words.txt index d435ed01dac..2ae7d88d6ba 100644 --- a/.codespell/ignore-words.txt +++ b/.codespell/ignore-words.txt @@ -28,3 +28,4 @@ ftbs ftb curren mabey +rsource diff --git a/ports/zephyr-cp/.gitignore b/ports/zephyr-cp/.gitignore index 65d8deaa722..7cfe421e6b4 100644 --- a/ports/zephyr-cp/.gitignore +++ b/ports/zephyr-cp/.gitignore @@ -1,7 +1,12 @@ -# West manages these folders. +# West manages these folders. modules/* are west clones, except iobroker, +# which is an in-tree Zephyr module (see modules/iobroker/README.md); its +# datasheets/ PDFs are licensed by Nordic and not redistributed here. bootloader build -modules -tools +modules/* +!modules/iobroker +modules/iobroker/datasheets/* +!modules/iobroker/datasheets/README.md +/tools zephyr .west diff --git a/ports/zephyr-cp/CMakeLists.txt b/ports/zephyr-cp/CMakeLists.txt index 9d115f1e176..e16e258a861 100644 --- a/ports/zephyr-cp/CMakeLists.txt +++ b/ports/zephyr-cp/CMakeLists.txt @@ -1,5 +1,11 @@ cmake_minimum_required(VERSION 3.20.0) +# The dynamic peripheral allocation / runtime pin routing code lives in a +# Zephyr module kept in-tree for now (modules/iobroker, see its README.md). +# It must be set before find_package(Zephyr). Repoint this at the module's new +# home to consume an externalized copy. +set(ZEPHYR_EXTRA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/modules/iobroker) + find_package(Zephyr REQUIRED HINTS lib/zephyr) project(circuitpython) diff --git a/ports/zephyr-cp/Kconfig b/ports/zephyr-cp/Kconfig index 015864100ec..1b9b76e1159 100644 --- a/ports/zephyr-cp/Kconfig +++ b/ports/zephyr-cp/Kconfig @@ -25,6 +25,17 @@ config UART_LINE_CTRL config ENTROPY_GENERATOR default y +# ===== Dynamic pin routing (nRF) ===== +# CircuitPython routes peripherals to pins at runtime on nRF SoCs. That needs +# pinctrl states to be swappable (PINCTRL_DYNAMIC) and devices to be +# de-initializable/re-initializable (DEVICE_DEINIT_SUPPORT). Both cost a little +# RAM (pinctrl configs move out of flash) and one function pointer per device. +config PINCTRL_DYNAMIC + default y if SOC_FAMILY_NORDIC_NRF + +config DEVICE_DEINIT_SUPPORT + default y if SOC_FAMILY_NORDIC_NRF + # ===== Bluetooth defaults ===== # Use a variable for the chosen name so the comma isn't parsed as an argument separator diff --git a/ports/zephyr-cp/Makefile b/ports/zephyr-cp/Makefile index 1fc5040fefa..2b328825b8f 100644 --- a/ports/zephyr-cp/Makefile +++ b/ports/zephyr-cp/Makefile @@ -41,7 +41,7 @@ CP_BOARD_CONF := $(DEBUG_CONF_FILE) endif endif ifneq ($(CP_BOARD_CONF),) -WEST_CMAKE_ARGS += -Dzephyr-cp_EXTRA_CONF_FILE=$(CP_BOARD_CONF) +WEST_CMAKE_ARGS += -Dzephyr-cp_EXTRA_CONF_FILE="$(CP_BOARD_CONF)" endif .PHONY: $(BUILD)/zephyr-cp/zephyr/zephyr.elf flash recover debug debug-jlink debugserver attach run run-sim clean menuconfig all clean-all sim clean-sim test fetch-port-submodules diff --git a/ports/zephyr-cp/README.md b/ports/zephyr-cp/README.md index d051097c83d..d7e5d169121 100644 --- a/ports/zephyr-cp/README.md +++ b/ports/zephyr-cp/README.md @@ -99,6 +99,49 @@ Behavior and precedence: - If neither is provided, defaults from `circuitpython.toml` are used. - Use `SHIELD=` (empty) to disable a board default shield for one build. +## Pin names + +Human readable pin names (the `board` module) come from the devicetree by +default: `gpio-leds` and `gpio-keys` labels, node aliases, and connector +`gpio-map`s. Boards can add names without any devicetree involvement by +listing them in `boards///circuitpython.toml` under `[pins]`. +Each entry maps a board module name to the pin number exposed by the board's +hardware: the SoC package pin (or, for ball grid array packages, the +datasheet's ball id, e.g. `"B2"`), or the castellated module pin when the +board uses a module like the Raytac MDBT50Q: + +```toml +[pins] +LED = 17 # QFN package pin number +SDA = "B2" # ball id for BGA/CSP packages +D13 = 8 # MDBT50Q-1MV2 module pin number +``` + +The build resolves each package pin to a SoC pad using the iobroker package +pin map selected by `CONFIG_IOBROKER_PACKAGE_` +(`modules/iobroker/packages/.toml`) and exposes the name on the +matching pad in the `board` module. A name that already maps to the same pin +(from the devicetree or an earlier entry) is deduplicated; a name that maps +to two different pins is a build error. The package map must not be `CUSTOM` +or missing, and the pad must be on an enabled GPIO controller; otherwise the +build fails with an error naming the offending entry. + +## Connector names + +Devicetree connector nodes (`gpio-map`) get their names from a generic +per-compatible list in `cptools/zephyr2cp.py`. A board whose silkscreen +differs can override them per position in `circuitpython.toml` under +`[connectors.]`, keying the gpio-map position (the header pin +number, as a string) to a name: + +```toml +[connectors.nordic_expansion_header] +0 = "EXP_00" # header GPIO 00 +21 = "EXP_21" # header GPIO 21 (QSPI CS) +``` + +Positions left out of the table get no name. + ## Testing other boards [Any Zephyr board](https://docs.zephyrproject.org/latest/boards/index.html#) can diff --git a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.conf b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.conf index cfa31e2fdcf..6b4d778740d 100644 --- a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.conf +++ b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.conf @@ -4,3 +4,7 @@ CONFIG_BOARD_SERIAL_BACKEND_CDC_ACM=n # Enable the ST7789V TFT via the Zephyr display subsystem CONFIG_DISPLAY=y + +# Pin numbers are module pins (MDBT50Q-1MV2 carries the aQFN73 inside). +# The CLUE uses the MDBT50Q-1MV2 module, so pin numbers are module pins. +CONFIG_IOBROKER_PACKAGE_MDBT50Q_1MV2=y diff --git a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.overlay b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.overlay index 586165dfe06..f39df2ad94e 100644 --- a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.overlay +++ b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/board.overlay @@ -1,10 +1,14 @@ +// The UF2 board definition points the Zephyr console/shell/mcumgr/BT chosen +// nodes at board_cdc_acm_uart, which is deleted above: CircuitPython talks USB +// CDC ACM through its own usb_cdc bindings instead, and no serial pins are +// claimed at boot. / { chosen { - zephyr,console = &uart0; - zephyr,shell-uart = &uart0; - zephyr,uart-mcumgr = &uart0; - zephyr,bt-mon-uart = &uart0; - zephyr,bt-c2h-uart = &uart0; + /delete-property/ zephyr,console; + /delete-property/ zephyr,shell-uart; + /delete-property/ zephyr,uart-mcumgr; + /delete-property/ zephyr,bt-mon-uart; + /delete-property/ zephyr,bt-c2h-uart; }; }; @@ -42,8 +46,86 @@ }; }; +// UART0, I2C1 and SPI3 are enabled with all pins disconnected and marked +// zephyr,deferred-init so nothing is claimed at boot: the iobroker +// initializes and routes a device to arbitrary pins when a busio object is +// constructed, and de-initializes it on release. (I2C0 is the fixed sensor +// bus, SPI2 the fixed display bus, and QSPI the fixed external flash.) +// +// I2C1 and SPI1 are the same peripheral instance (0x40004000), so only one +// mode can be enabled: I2C is the more useful dynamic bus on the CLUE. +// SPI1 is left disabled; UART1 is enabled by neither overlay because UART0 +// already provides a dynamic serial instance. + +&pinctrl { + uart0_dyn_default: uart0_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart0_dyn_sleep: uart0_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + i2c1_dyn_default: i2c1_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c1_dyn_sleep: i2c1_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + spi3_dyn_default: spi3_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi3_dyn_sleep: spi3_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; +}; + &uart0 { - status = "okay"; + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart0_dyn_default>; + pinctrl-1 = <&uart0_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&i2c1 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c1_dyn_default>; + pinctrl-1 = <&i2c1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi3 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi3_dyn_default>; + pinctrl-1 = <&spi3_dyn_sleep>; + pinctrl-names = "default", "sleep"; }; #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/circuitpython.toml index 4af0c99d878..01eeb66eed0 100644 --- a/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/circuitpython.toml +++ b/ports/zephyr-cp/boards/adafruit/clue_nrf52840_zephyr/circuitpython.toml @@ -5,3 +5,86 @@ NAME="CLUE nRF52840 Express" # Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. counterpart = "nordic/clue_nrf52840_express" + + +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# through the package pin map selected in board.conf. + +[pins] +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# to MDBT50Q-1MV2 module pin numbers through the package pin map selected +# in board.conf. +P0 = 20 +D0 = 20 +A2 = 20 +RX = 20 +P1 = 21 +D1 = 21 +A3 = 21 +TX = 21 +P2 = 9 +D2 = 9 +A4 = 9 +P3 = 13 +D3 = 13 +A5 = 13 +P4 = 11 +D4 = 11 +A6 = 11 +P5 = 50 +D5 = 50 +BUTTON_A = 50 +P6 = 26 +D6 = 26 +P7 = 23 +D7 = 23 +P8 = 58 +D8 = 58 +P9 = 16 +D9 = 16 +P10 = 14 +D10 = 14 +A7 = 14 +P11 = 3 +D11 = 3 +BUTTON_B = 3 +P12 = 12 +D12 = 12 +A0 = 12 +P13 = 24 +D13 = 24 +SCK = 24 +P14 = 22 +D14 = 22 +MISO = 22 +P15 = 19 +D15 = 19 +MOSI = 19 +P16 = 10 +D16 = 10 +A1 = 10 +P17 = 61 +D17 = 61 +L = 61 +LED = 61 +P18 = 38 +D18 = 38 +NEOPIXEL = 38 +P19 = 49 +D19 = 49 +SCL = 49 +P20 = 48 +D20 = 48 +SDA = 48 +MICROPHONE_CLOCK = 18 +MICROPHONE_DATA = 17 +SPEAKER = 47 +PROXIMITY_LIGHT_INTERRUPT = 52 +ACCELEROMETER_GYRO_INTERRUPT = 57 +WHITE_LEDS = 54 +TFT_RESET = 60 +TFT_BACKLIGHT = 59 +TFT_CS = 29 +TFT_DC = 37 +TFT_SCK = 36 +TFT_MOSI = 39 diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.conf b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.conf index 6d7299ec6e6..799f4a727ba 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.conf +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.conf @@ -1,3 +1,7 @@ CONFIG_USE_DT_CODE_PARTITION=y CONFIG_BOARD_SERIAL_BACKEND_CDC_ACM=n + +# Pin numbers are module pins (MDBT50Q-1MV2 carries the aQFN73 inside). +# The feathers use the MDBT50Q-1MV2 module, so pin numbers are module pins. +CONFIG_IOBROKER_PACKAGE_MDBT50Q_1MV2=y diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.overlay b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.overlay index 586165dfe06..2065ba5e433 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.overlay +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/board.overlay @@ -42,6 +42,110 @@ }; }; +// Bus instances enabled with all pins disconnected and marked +// zephyr,deferred-init so nothing is claimed at boot: the iobroker +// initializes and routes a device to arbitrary pins when a busio object is +// constructed, and de-initializes it on release: I2C1, SPI2, SPI3 and UART1. +// (The fixed I2C0 is the Stemma QT bus with the SHT31 sensor, and UART0 the +// console. The formerly fixed SPI1 is dropped: it had no device attached and +// its instance is the same peripheral as I2C1, so only one mode can be +// enabled.) + +&pinctrl { + i2c1_dyn_default: i2c1_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c1_dyn_sleep: i2c1_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + spi2_dyn_default: spi2_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi2_dyn_sleep: spi2_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + spi3_dyn_default: spi3_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi3_dyn_sleep: spi3_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + uart1_dyn_default: uart1_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart1_dyn_sleep: uart1_dyn_sleep { + group1 { + psels = , + ; + }; + }; +}; + +&i2c1 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c1_dyn_default>; + pinctrl-1 = <&i2c1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi2 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi2_dyn_default>; + pinctrl-1 = <&spi2_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi3 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi3_dyn_default>; + pinctrl-1 = <&spi3_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&uart1 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart1_dyn_default>; + pinctrl-1 = <&uart1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + &uart0 { status = "okay"; }; diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/circuitpython.toml index 5dfa12f79df..78a48bc36c0 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/circuitpython.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/circuitpython.toml @@ -5,3 +5,49 @@ NAME="Feather Bluefruit Sense" # Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. counterpart = "nordic/feather_bluefruit_sense" + + +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# through the package pin map selected in board.conf. + +[pins] +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# to MDBT50Q-1MV2 module pin numbers through the package pin map selected +# in board.conf. +A0 = 20 +A1 = 21 +A2 = 14 +A3 = 13 +A4 = 11 +A5 = 9 +AREF = 12 +VOLTAGE_MONITOR = 10 +BATTERY = 10 +SWITCH = 50 +NFC1 = 52 +NFC2 = 54 +D2 = 54 +D3 = 4 +D5 = 25 +D6 = 23 +D9 = 19 +D10 = 16 +D11 = 22 +D12 = 24 +D13 = 26 +NEOPIXEL = 38 +SCK = 36 +MOSI = 37 +MISO = 39 +TX = 49 +RX = 48 +SCL = 27 +SDA = 29 +L = 26 +LED = 26 +RED_LED = 26 +BLUE_LED = 3 +MICROPHONE_CLOCK = 18 +MICROPHONE_DATA = 17 +PROXIMITY_LIGHT_INTERRUPT = 47 +ACCELEROMETER_GYRO_INTERRUPT = 4 diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.conf b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.conf index 6d7299ec6e6..799f4a727ba 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.conf +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.conf @@ -1,3 +1,7 @@ CONFIG_USE_DT_CODE_PARTITION=y CONFIG_BOARD_SERIAL_BACKEND_CDC_ACM=n + +# Pin numbers are module pins (MDBT50Q-1MV2 carries the aQFN73 inside). +# The feathers use the MDBT50Q-1MV2 module, so pin numbers are module pins. +CONFIG_IOBROKER_PACKAGE_MDBT50Q_1MV2=y diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.overlay b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.overlay index 586165dfe06..ea1eeca9843 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.overlay +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/board.overlay @@ -42,6 +42,109 @@ }; }; +// Bus instances enabled with all pins disconnected and marked +// zephyr,deferred-init so nothing is claimed at boot: the iobroker +// initializes and routes a device to arbitrary pins when a busio object is +// constructed, and de-initializes it on release: I2C1, SPI2, SPI3 and UART1. +// (The fixed I2C0 is the Stemma QT bus and UART0 the console. The formerly +// fixed SPI1 is dropped: it had no device attached and its instance is the +// same peripheral as I2C1, so only one mode can be enabled.) + +&pinctrl { + i2c1_dyn_default: i2c1_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c1_dyn_sleep: i2c1_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + spi2_dyn_default: spi2_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi2_dyn_sleep: spi2_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + spi3_dyn_default: spi3_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi3_dyn_sleep: spi3_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + uart1_dyn_default: uart1_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart1_dyn_sleep: uart1_dyn_sleep { + group1 { + psels = , + ; + }; + }; +}; + +&i2c1 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c1_dyn_default>; + pinctrl-1 = <&i2c1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi2 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi2_dyn_default>; + pinctrl-1 = <&spi2_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi3 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi3_dyn_default>; + pinctrl-1 = <&spi3_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&uart1 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart1_dyn_default>; + pinctrl-1 = <&uart1_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + &uart0 { status = "okay"; }; diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/circuitpython.toml index 8f97aca1d3d..58a36de1eba 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/circuitpython.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/circuitpython.toml @@ -5,3 +5,46 @@ NAME="Feather nRF52840 Express" # Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. counterpart = "nordic/feather_nrf52840_express" + + +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# through the package pin map selected in board.conf. + +[pins] +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# to MDBT50Q-1MV2 module pin numbers through the package pin map selected +# in board.conf. +A0 = 20 +A1 = 21 +A2 = 14 +A3 = 13 +A4 = 11 +A5 = 9 +AREF = 12 +VOLTAGE_MONITOR = 10 +BATTERY = 10 +SWITCH = 50 +NFC1 = 52 +NFC2 = 54 +D2 = 54 +D5 = 25 +D6 = 23 +D9 = 19 +D10 = 16 +D11 = 22 +D12 = 24 +D13 = 26 +NEOPIXEL = 38 +NEOPIXEL_POWER = 7 +SCK = 36 +MOSI = 37 +MISO = 39 +TX = 49 +RX = 48 +SCL = 27 +SDA = 29 +L = 8 +LED = 8 +RED_LED = 8 +D3 = 8 +BLUE_LED = 3 diff --git a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/board.conf b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/board.conf index 91c3c15b37d..be49c1e7940 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/board.conf +++ b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/board.conf @@ -1 +1,4 @@ CONFIG_GPIO=y + +# Package pin map for the RP2040 QFN-56 used by this board. +CONFIG_IOBROKER_PACKAGE_RP2040_QFN56=y diff --git a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/circuitpython.toml b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/circuitpython.toml index 4e992a212f4..c6f089958da 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/circuitpython.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/circuitpython.toml @@ -2,3 +2,35 @@ CIRCUITPY_BUILD_EXTENSIONS = ["elf", "uf2"] # Non-Zephyr build of the same board; nvm and CIRCUITPY must sit where it puts them. counterpart = "raspberrypi/adafruit_feather_rp2040" + + +# Pin names copied from the ports/ counterpart board's pins.c and resolved +# through the package pin map selected in board.conf. +[pins] +A0 = 38 +A1 = 39 +A2 = 40 +A3 = 41 +D24 = 36 +D25 = 37 +SCK = 29 +MOSI = 30 +MISO = 31 +D0 = 3 +RX = 3 +D1 = 2 +TX = 2 +D4 = 8 +SDA = 4 +SCL = 5 +D5 = 9 +D6 = 11 +D9 = 12 +D10 = 13 +D11 = 14 +D12 = 15 +LED = 16 +D13 = 16 +BUTTON = 6 +BOOT = 6 +NEOPIXEL = 27 diff --git a/ports/zephyr-cp/boards/nordic/nrf5340dk/board.overlay b/ports/zephyr-cp/boards/nordic/nrf5340dk/board.overlay index 6b7ee85de7c..2842e7a3f98 100644 --- a/ports/zephyr-cp/boards/nordic/nrf5340dk/board.overlay +++ b/ports/zephyr-cp/boards/nordic/nrf5340dk/board.overlay @@ -29,4 +29,63 @@ i2s_rxtx: &i2s0 { /delete-node/ &slot1_partition; +// Enable free serial peripheral instances so CircuitPython can mux them to +// arbitrary pins at runtime. Each instance is enabled in one mode because the +// i2c/spi/uart nodes of an instance are the same peripheral (enforced by +// zephyr/soc/nordic/validate_enabled_instances.c). All signals are +// disconnected and the devices are marked zephyr,deferred-init so nothing is +// claimed at boot; iobroker initializes and routes a device when a busio +// object is constructed and de-initializes it on release. +// +// Instance 0 is left alone: uart0 is the console and i2c0/spi0 share that +// same peripheral, so they must not be enabled. Instance 1 stays with the +// fixed arduino header i2c1, and spi4 stays the fixed arduino header SPI. + +&pinctrl { + i2c2_dyn_default: i2c2_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c2_dyn_sleep: i2c2_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + uart3_dyn_default: uart3_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart3_dyn_sleep: uart3_dyn_sleep { + group1 { + psels = , + ; + }; + }; +}; + +&i2c2 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c2_dyn_default>; + pinctrl-1 = <&i2c2_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&uart3 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart3_dyn_default>; + pinctrl-1 = <&uart3_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/nordic/nrf54h20dk/board.overlay b/ports/zephyr-cp/boards/nordic/nrf54h20dk/board.overlay index e557179992b..9461fa34569 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54h20dk/board.overlay +++ b/ports/zephyr-cp/boards/nordic/nrf54h20dk/board.overlay @@ -45,11 +45,104 @@ }; /* Remove slot1 (OTA), expand slot0 to use the space. - * CircuitPython doesn't use OTA updates. */ + * CircuitPython doesn't use OTA updates. The expansion absorbs the + * cpurad slot0 as well: no cpurad image is built (CONFIG_BT=n), so its + * slot would otherwise be dead space between cpuapp slot0 and the + * cpuppr code partition at 0xe4000. */ &slot0_partition { reg = <0x40000 DT_SIZE_K(656)>; }; /delete-node/ &slot1_partition; +/delete-node/ &cpurad_slot0_partition; + +// Enable free serial peripheral instances so CircuitPython can mux them to +// arbitrary pins at runtime. On this SoC the i2c/spi/uart nodes with the same +// instance number are the same peripheral block (e.g. i2c130/spi130/uart130 +// all sit at 0x9a5000), so each instance is enabled in exactly one mode. All +// signals are disconnected and the devices are marked zephyr,deferred-init so +// nothing is claimed at boot; iobroker initializes and routes a device when a +// busio object is constructed and de-initializes it on release. +// +// Instance 130 stays with the fixed arduino header i2c130. Instances 120, +// 135 and 136 are left alone: uart120 and uart135 are the console UARTs of +// the (not built here) FLPR and PPR cores, and uart136 is the app console. +// The DMA region is required on this SoC: the serial drivers only access +// memory the peripheral's EasyDMA is allowed to reach. + +&pinctrl { + i2c131_dyn_default: i2c131_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c131_dyn_sleep: i2c131_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + spi132_dyn_default: spi132_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi132_dyn_sleep: spi132_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + uart137_dyn_default: uart137_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart137_dyn_sleep: uart137_dyn_sleep { + group1 { + psels = , + ; + }; + }; +}; + +&i2c131 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c131_dyn_default>; + pinctrl-1 = <&i2c131_dyn_sleep>; + pinctrl-names = "default", "sleep"; + zephyr,concat-buf-size = <256>; + memory-regions = <&cpuapp_dma_region>; +}; + +&spi132 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi132_dyn_default>; + pinctrl-1 = <&spi132_dyn_sleep>; + pinctrl-names = "default", "sleep"; + memory-regions = <&cpuapp_dma_region>; +}; + +&uart137 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart137_dyn_default>; + pinctrl-1 = <&uart137_dyn_sleep>; + pinctrl-names = "default", "sleep"; + memory-regions = <&cpuapp_dma_region>; +}; #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15dk/board.overlay b/ports/zephyr-cp/boards/nordic/nrf54l15dk/board.overlay index 7ffd2dec4d6..e4dc1bc7802 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15dk/board.overlay +++ b/ports/zephyr-cp/boards/nordic/nrf54l15dk/board.overlay @@ -1,28 +1,90 @@ // nRF54L15 DK doesn't have USB, so no app.overlay for CDC ACM. -// I2C bus on P1.8 (SDA) and P1.10 (SCL). +// Enable every free serial peripheral instance so CircuitPython can mux it to +// arbitrary pins at runtime. Each instance is enabled in one mode because the +// i2c/spi/uart nodes of an instance are the same peripheral (enforced by +// zephyr/soc/nordic/validate_enabled_instances.c). All signals are +// disconnected and the devices are marked zephyr,deferred-init so nothing is +// claimed at boot; iobroker initializes and routes a device when a busio +// object is constructed and de-initializes it on release. +// +// Instance 20 is left alone: uart20 is the console (main UART) and i2c20/spi20 +// share that same peripheral, so they must not be enabled. Instance 00 is +// owned by spi00 and the MX25R64 SPI flash (no TWI00 on this SoC). + &pinctrl { - i2c21_default: i2c21_default { + i2c21_dyn_default: i2c21_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c21_dyn_sleep: i2c21_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + spi22_dyn_default: spi22_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi22_dyn_sleep: spi22_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + uart30_dyn_default: uart30_dyn_default { group1 { - psels = , - ; - bias-pull-up; + psels = , + ; }; }; - i2c21_sleep: i2c21_sleep { + uart30_dyn_sleep: uart30_dyn_sleep { group1 { - psels = , - ; - low-power-enable; + psels = , + ; }; }; }; &i2c21 { - clock-frequency = ; - pinctrl-0 = <&i2c21_default>; - pinctrl-1 = <&i2c21_sleep>; + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c21_dyn_default>; + pinctrl-1 = <&i2c21_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi22 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi22_dyn_default>; + pinctrl-1 = <&spi22_dyn_sleep>; pinctrl-names = "default", "sleep"; +}; + +&uart30 { status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart30_dyn_default>; + pinctrl-1 = <&uart30_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +/ { + chosen { + zephyr,code-partition = &slot0_partition; + }; }; diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.conf b/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.conf index 2627ebe2e5f..b679a257de5 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.conf +++ b/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.conf @@ -2,3 +2,9 @@ CONFIG_SERIAL=y CONFIG_CONSOLE=y CONFIG_UART_CONSOLE=y + +# Enable the controller's Zephyr VS HCI set/get TX power commands, which +# _bleio's start_advertising(tx_power=...) uses. Without this the controller +# rejects the commands and tx power is fixed at the BT_CTLR_TX_PWR default (0 dBm). +# The nRF54L15 radio supports -46 to +8 dBm. +CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.overlay b/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.overlay index ddf5c8f7e8f..6dd08bfce59 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.overlay +++ b/ports/zephyr-cp/boards/nordic/nrf54l15tag/board.overlay @@ -37,3 +37,65 @@ zephyr,uart-mcumgr = &uart30; }; }; + +// Enable free serial peripheral instances so CircuitPython can mux them to +// arbitrary pins at runtime. Each instance is enabled in one mode because the +// i2c/spi/uart nodes of an instance are the same peripheral (enforced by +// zephyr/soc/nordic/validate_enabled_instances.c). All signals are +// disconnected and the devices are marked zephyr,deferred-init so nothing is +// claimed at boot; iobroker initializes and routes a device when a busio +// object is constructed and de-initializes it on release. +// +// Instance 21 stays with the fixed i2c21 (bme688 and adxl367 hang off it) and +// instance 22 with the fixed spi22 (bmi270). Instance 30 is left alone: +// uart30 is the console above and i2c30/spi30 share that same peripheral, so +// they must not be enabled. There is no i2c00 on this SoC, so instance 00 can +// only carry spi00 (or uart00). + +&pinctrl { + i2c20_dyn_default: i2c20_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c20_dyn_sleep: i2c20_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + spi00_dyn_default: spi00_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi00_dyn_sleep: spi00_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; +}; + +&i2c20 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c20_dyn_default>; + pinctrl-1 = <&i2c20_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi00 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi00_dyn_default>; + pinctrl-1 = <&spi00_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; diff --git a/ports/zephyr-cp/boards/nordic/nrf54lm20dk/board.overlay b/ports/zephyr-cp/boards/nordic/nrf54lm20dk/board.overlay index 0f48a5a5d46..636f481718e 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54lm20dk/board.overlay +++ b/ports/zephyr-cp/boards/nordic/nrf54lm20dk/board.overlay @@ -24,29 +24,131 @@ }; }; -// I2C bus on P1.13 (SDA) and P1.23 (SCL). +// Enable every free serial peripheral instance so CircuitPython can mux it to +// arbitrary pins at runtime. Each instance is enabled in one mode because the +// i2c/spi/uart nodes of an instance are the same peripheral (enforced by +// zephyr/soc/nordic/validate_enabled_instances.c). All signals are +// disconnected and the devices are marked zephyr,deferred-init so nothing is +// claimed at boot; iobroker initializes and routes a device when a busio +// object is constructed and de-initializes it on release. +// +// Instance 20 is left alone: uart20 is the console (main UART) and i2c20/spi20 +// share that same peripheral, so they must not be enabled. Instance 00 is +// owned by spi00 and the MX25R64 SPI flash, so uart00 must not be enabled. + &pinctrl { - i2c21_default: i2c21_default { + i2c21_dyn_default: i2c21_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c21_dyn_sleep: i2c21_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + i2c23_dyn_default: i2c23_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c23_dyn_sleep: i2c23_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + spi22_dyn_default: spi22_dyn_default { + group1 { + psels = , + , + ; + }; + }; + + spi22_dyn_sleep: spi22_dyn_sleep { + group1 { + psels = , + , + ; + }; + }; + + spi24_dyn_default: spi24_dyn_default { group1 { - psels = , - ; - bias-pull-up; + psels = , + , + ; }; }; - i2c21_sleep: i2c21_sleep { + spi24_dyn_sleep: spi24_dyn_sleep { group1 { - psels = , - ; - low-power-enable; + psels = , + , + ; + }; + }; + + uart30_dyn_default: uart30_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart30_dyn_sleep: uart30_dyn_sleep { + group1 { + psels = , + ; }; }; }; &i2c21 { - clock-frequency = ; - pinctrl-0 = <&i2c21_default>; - pinctrl-1 = <&i2c21_sleep>; + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c21_dyn_default>; + pinctrl-1 = <&i2c21_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&i2c23 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c23_dyn_default>; + pinctrl-1 = <&i2c23_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi22 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi22_dyn_default>; + pinctrl-1 = <&spi22_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&spi24 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&spi24_dyn_default>; + pinctrl-1 = <&spi24_dyn_sleep>; pinctrl-names = "default", "sleep"; +}; + +&uart30 { status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart30_dyn_default>; + pinctrl-1 = <&uart30_dyn_sleep>; + pinctrl-names = "default", "sleep"; }; diff --git a/ports/zephyr-cp/boards/nordic/nrf54lm20dk/circuitpython.toml b/ports/zephyr-cp/boards/nordic/nrf54lm20dk/circuitpython.toml index 83e6bcd39c4..460f0f32cfc 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54lm20dk/circuitpython.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54lm20dk/circuitpython.toml @@ -1 +1,115 @@ CIRCUITPY_BUILD_EXTENSIONS = ["hex"] + +# Headers silkscreened PORT0 (P0.00-P0.09), PORT1:00-15 (P1.00-P1.15), +# PORT1:16-31 (P1.16-P1.31), PORT2 (P2.00-P2.10) and PORT3 (P3.00-P3.12), +# resolved to CSP98 ball names through the package map selected in board.conf. + +[pins] +PORT0_00 = "K3" +PORT0_01 = "K2" +PORT0_02 = "J3" +PORT0_03 = "J2" +PORT0_04 = "J1" +PORT0_05 = "H3" +PORT0_06 = "H2" +PORT0_07 = "G3" +PORT0_08 = "F3" +PORT0_09 = "E3" + +# P1.01 (NFC1) and P1.02 (NFC2) are routed to the NFC antenna by default; +# move R33/R34 to R3/R4 and disable the NFCT peripheral to use them as GPIO. +PORT1_00 = "K8" +PORT1_01 = "J6" +PORT1_02 = "J7" +PORT1_03 = "H9" +PORT1_04 = "J9" +PORT1_05 = "H8" +PORT1_06 = "J8" +PORT1_07 = "G8" +PORT1_08 = "F8" +PORT1_09 = "E8" +PORT1_10 = "D5" +PORT1_11 = "C5" +PORT1_12 = "C4" +PORT1_13 = "C3" +PORT1_14 = "C2" +PORT1_15 = "B2" +PORT1_16 = "B3" +PORT1_17 = "B4" +PORT1_18 = "B5" +PORT1_19 = "B6" +# P1.20/P1.21 feed the 32.768 kHz crystal by default; short SB3/SB4 (and cut +# SB1/SB2) to use them as GPIO. +PORT1_22 = "D6" +PORT1_23 = "B7" +PORT1_24 = "D7" +PORT1_25 = "E7" +PORT1_26 = "B8" +PORT1_27 = "C8" +PORT1_28 = "D8" +PORT1_29 = "H10" +PORT1_30 = "J10" +PORT1_31 = "K9" + +# P2.00-P2.05 are shared with the MX25R64 external flash. +PORT2_00 = "B10" +PORT2_01 = "C10" +PORT2_02 = "D10" +PORT2_03 = "E10" +PORT2_04 = "F10" +PORT2_05 = "G9" +PORT2_06 = "B9" +PORT2_07 = "C9" +PORT2_08 = "D9" +PORT2_09 = "E9" +PORT2_10 = "F9" + +PORT3_00 = "H7" +PORT3_01 = "H6" +PORT3_02 = "H5" +PORT3_03 = "H4" +PORT3_04 = "J4" +PORT3_05 = "G7" +PORT3_06 = "G6" +PORT3_07 = "G5" +PORT3_08 = "G4" +PORT3_09 = "F7" +PORT3_10 = "F4" +PORT3_11 = "E4" +PORT3_12 = "D4" + +# Silkscreen button names. LED0-LED3, SW0-SW3 and PUSH_BUTTON_0-3 already come +# from the devicetree. +BUTTON0 = "B8" +BUTTON1 = "E8" +BUTTON2 = "F8" +BUTTON3 = "H3" + +# Nordic expansion board header (P17), named per the board silkscreen. +# Positions key the gpio-map order (header GPIO 00-21, odd/even columns from +# the A0/B0 end); I0 sits opposite the header's mid GND and has no gpio-map +# entry. Function groups check out: B0 = PWM (P3.04), D0-D3 = SPI +# (P3.00-P3.03), H0-H5 = QSPI (P2.00-P2.05). +[connectors.nordic_expansion_header] +0 = "A0" +1 = "B0" +2 = "C0" +3 = "C1" +4 = "C2" +5 = "C3" +6 = "D0" +7 = "D1" +8 = "E2" +9 = "E1" +10 = "D2" +11 = "D3" +12 = "F0" +13 = "F1" +14 = "C4" +15 = "G0" +16 = "H0" +17 = "H1" +18 = "H2" +19 = "H3" +20 = "H4" +21 = "H5" diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/board.overlay b/ports/zephyr-cp/boards/nordic/nrf7002dk/board.overlay index cec4fbb2b98..70f39cd19e3 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/board.overlay +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/board.overlay @@ -10,7 +10,72 @@ reg = <0x00000000 0x0000C000>; }; +&storage_partition { + reg = <0x000FC000 0x00004000>; +}; + /delete-node/ &slot1_partition; /delete-node/ &storage_partition; +// Enable free serial peripheral instances so CircuitPython can mux them to +// arbitrary pins at runtime. Each instance is enabled in one mode because the +// i2c/spi/uart nodes of an instance are the same peripheral (enforced by +// zephyr/soc/nordic/validate_enabled_instances.c). All signals are +// disconnected and the devices are marked zephyr,deferred-init so nothing is +// claimed at boot; iobroker initializes and routes a device when a busio +// object is constructed and de-initializes it on release. +// +// Instance 0 is left alone: uart0 is the console and i2c0/spi0 share that +// same peripheral, so they must not be enabled. Instance 1 stays with the +// fixed arduino header i2c1, and spi4 stays fixed because it owns the +// MX25R64 flash. The disabled arduino spi3/uart1 nodes keep their devicetree +// pinout in case a shield wants them. + +&pinctrl { + i2c2_dyn_default: i2c2_dyn_default { + group1 { + psels = , + ; + }; + }; + + i2c2_dyn_sleep: i2c2_dyn_sleep { + group1 { + psels = , + ; + }; + }; + + uart3_dyn_default: uart3_dyn_default { + group1 { + psels = , + ; + }; + }; + + uart3_dyn_sleep: uart3_dyn_sleep { + group1 { + psels = , + ; + }; + }; +}; + +&i2c2 { + status = "okay"; + zephyr,deferred-init; + pinctrl-0 = <&i2c2_dyn_default>; + pinctrl-1 = <&i2c2_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&uart3 { + status = "okay"; + zephyr,deferred-init; + current-speed = <115200>; + pinctrl-0 = <&uart3_dyn_default>; + pinctrl-1 = <&uart3_dyn_sleep>; + pinctrl-names = "default", "sleep"; +}; + #include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/board.conf b/ports/zephyr-cp/boards/renesas/ek_ra6m5/board.conf new file mode 100644 index 00000000000..57dea741be8 --- /dev/null +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/board.conf @@ -0,0 +1,3 @@ +# Disable BIN output; Renesas RA OFS registers at high addresses create +# ~16 MB binaries. HEX output (already the SoC default) is sparse. +CONFIG_BUILD_OUTPUT_BIN=n diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/board.overlay b/ports/zephyr-cp/boards/renesas/ek_ra6m5/board.overlay new file mode 100644 index 00000000000..42663b3acf4 --- /dev/null +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/board.overlay @@ -0,0 +1,31 @@ +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + /* The app links at offset 0 (no zephyr,code-partition is set, + * so FLASH_LOAD_OFFSET stays 0) and CIRCUITPY gets the second + * half of the 2 MB internal code flash. + */ + circuitpy_partition: partition@100000 { + label = "circuitpy"; + reg = <0x100000 DT_SIZE_M(1)>; + }; + }; +}; + +/* Disable OFS (Option Function Select) nodes so their high-address + * sections don't end up in the app HEX. + */ +&option_setting_ofs0 { status = "disabled"; }; +&option_setting_dualsel { status = "disabled"; }; +&option_setting_ofs1_sec { status = "disabled"; }; +&option_setting_banksel_sec { status = "disabled"; }; +&option_setting_bps_sec { status = "disabled"; }; +&option_setting_pbps_sec { status = "disabled"; }; +&option_setting_ofs1_sel { status = "disabled"; }; +&option_setting_banksel_sel { status = "disabled"; }; +&option_setting_bps_sel { status = "disabled"; }; + +#include "../../../app.overlay" diff --git a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/board.conf b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/board.conf new file mode 100644 index 00000000000..b0948df7a13 --- /dev/null +++ b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/board.conf @@ -0,0 +1,3 @@ +# No USB, button, or retention device available. The stale UF2/MCUboot +# options are not set here because they only exist in the MCUboot image's +# Kconfig, which this board does not build. diff --git a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/board.overlay b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/board.overlay new file mode 100644 index 00000000000..37011475f9a --- /dev/null +++ b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/board.overlay @@ -0,0 +1,28 @@ +/ { + chosen { + zephyr,flash-controller = &xspi2; + zephyr,flash = &mx25um51245g; + zephyr,code-partition = &slot0_partition; + }; +}; + +/* Code and CIRCUITPY live on the 64 MB external XSPI NOR flash. The board + * dts only declares a storage partition (last sector, shrunk for an errata + * workaround); add the code slot and the filesystem here. The image runs + * from RAM, so slot0 is only a load address. + */ +&mx25um51245g { + partitions { + slot0_partition: partition@0 { + label = "image-0"; + reg = <0x0 DT_SIZE_M(2)>; + }; + + circuitpy_partition: partition@200000 { + label = "circuitpy"; + reg = <0x200000 (DT_SIZE_M(64) - DT_SIZE_M(2) - DT_SIZE_K(64))>; + }; + }; +}; + +#include "../../../app.overlay" diff --git a/ports/zephyr-cp/common-hal/busio/I2C.c b/ports/zephyr-cp/common-hal/busio/I2C.c index 84e95721b27..d77fe960c7e 100644 --- a/ports/zephyr-cp/common-hal/busio/I2C.c +++ b/ports/zephyr-cp/common-hal/busio/I2C.c @@ -5,9 +5,14 @@ // SPDX-License-Identifier: MIT #include "shared-bindings/busio/I2C.h" +#include "shared-bindings/microcontroller/Pin.h" + +#include "bindings/zephyr_kernel/__init__.h" #include "py/mperrno.h" #include "py/runtime.h" +#include + #include #include #include @@ -18,30 +23,87 @@ mp_obj_t common_hal_busio_i2c_construct_from_device(busio_i2c_obj_t *self, const self->i2c_device = i2c_device; k_mutex_init(&self->mutex); self->has_lock = false; + self->dynamic = false; + self->sda = NULL; + self->scl = NULL; return MP_OBJ_FROM_PTR(self); } -// Standard busio construct - not used in Zephyr port (devices come from device tree) +static void raise_no_i2c_peripheral(int err) { + if (err == -ENODEV) { + mp_raise_ValueError(MP_ERROR_TEXT("All I2C peripherals are in use")); + } + if (err == -EBUSY) { + mp_raise_ValueError(MP_ERROR_TEXT("Internal resource(s) in use")); + } + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_I2C); +} + +// Standard busio construct: pick a free peripheral instance and route it to +// the requested pins at runtime (supported on nRF SoCs). void common_hal_busio_i2c_construct(busio_i2c_obj_t *self, const mcu_pin_obj_t *scl, const mcu_pin_obj_t *sda, uint32_t frequency, uint32_t timeout_ms) { - mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_I2C); + const struct device *dev = NULL; + int ret = iobroker_i2c_allocate(sda->package_pin, scl->package_pin, &dev); + if (ret < 0) { + raise_no_i2c_peripheral(ret); + } + + common_hal_busio_i2c_construct_from_device(self, dev); + self->dynamic = true; + self->sda = sda; + self->scl = scl; + + // Initialize the deferred device now that it is routed to the requested + // pins. Fixed devicetree instances are already initialized (-EALREADY). + int init_ret = device_init(dev); + if (init_ret < 0 && init_ret != -EALREADY) { + // The failed init may have routed pins and left the device in a + // partial state; deinit gives up the claim and resets the pins. + common_hal_busio_i2c_deinit(self); + raise_zephyr_error(init_ret); + } + + // Apply the requested bus frequency. Zephyr nRF drivers support 100k, + // 400k and (on TWIM) 1M. + uint8_t speed; + if (frequency <= 100000) { + speed = I2C_SPEED_STANDARD; + } else if (frequency <= 400000) { + speed = I2C_SPEED_FAST; + } else { + speed = I2C_SPEED_FAST_PLUS; + } + int config_ret = i2c_configure(self->i2c_device, I2C_SPEED_SET(speed)); + if (config_ret < 0) { + common_hal_busio_i2c_deinit(self); + raise_zephyr_error(config_ret); + } } bool common_hal_busio_i2c_deinited(busio_i2c_obj_t *self) { - // Always leave it active (managed by Zephyr) - return false; + return self->i2c_device == NULL; } void common_hal_busio_i2c_deinit(busio_i2c_obj_t *self) { if (common_hal_busio_i2c_deinited(self)) { return; } - // Always leave it active (managed by Zephyr) + if (self->dynamic) { + // The release de-inits the device, which applies its low-power + // pinctrl state and leaves the routed pins disconnected. + (void)iobroker_release(self->i2c_device); + self->sda = NULL; + self->scl = NULL; + self->i2c_device = NULL; + } } void common_hal_busio_i2c_mark_deinit(busio_i2c_obj_t *self) { - // Not needed for Zephyr port + if (self->dynamic) { + self->i2c_device = NULL; + } } bool common_hal_busio_i2c_probe(busio_i2c_obj_t *self, uint8_t addr) { diff --git a/ports/zephyr-cp/common-hal/busio/I2C.h b/ports/zephyr-cp/common-hal/busio/I2C.h index 4fa877739b7..fcaa0298a8a 100644 --- a/ports/zephyr-cp/common-hal/busio/I2C.h +++ b/ports/zephyr-cp/common-hal/busio/I2C.h @@ -7,6 +7,8 @@ #pragma once #include "py/obj.h" + +#include "common-hal/microcontroller/Pin.h" #include typedef struct { @@ -14,6 +16,12 @@ typedef struct { const struct device *i2c_device; struct k_mutex mutex; bool has_lock; + // True when the underlying Zephyr device was dynamically routed to the + // pins below at construction time. Such objects deinitialize the device + // and release their pins. + bool dynamic; + const mcu_pin_obj_t *sda; + const mcu_pin_obj_t *scl; } busio_i2c_obj_t; // Helper function to construct from Zephyr device tree device diff --git a/ports/zephyr-cp/common-hal/busio/SPI.c b/ports/zephyr-cp/common-hal/busio/SPI.c index 2864c90b490..bb2342ee113 100644 --- a/ports/zephyr-cp/common-hal/busio/SPI.c +++ b/ports/zephyr-cp/common-hal/busio/SPI.c @@ -5,12 +5,18 @@ // SPDX-License-Identifier: MIT #include "shared-bindings/busio/SPI.h" +#include "shared-bindings/microcontroller/Pin.h" + #include "py/mperrno.h" #include "py/runtime.h" #include "py/gc.h" #include "shared/runtime/interrupt_char.h" #include "supervisor/port.h" +#include "bindings/zephyr_kernel/__init__.h" + +#include +#include #include #include #include @@ -22,6 +28,10 @@ mp_obj_t common_hal_busio_spi_construct_from_device(busio_spi_obj_t *self, const k_mutex_init(&self->mutex); self->has_lock = false; self->active_config = 0; + self->dynamic = false; + self->clock = NULL; + self->mosi = NULL; + self->miso = NULL; k_poll_signal_init(&self->signal); @@ -34,27 +44,69 @@ mp_obj_t common_hal_busio_spi_construct_from_device(busio_spi_obj_t *self, const return MP_OBJ_FROM_PTR(self); } -// Standard busio construct - not used in Zephyr port (devices come from device tree) +// Standard busio construct: pick a free peripheral instance and route it to +// the requested pins at runtime (supported on nRF SoCs). void common_hal_busio_spi_construct(busio_spi_obj_t *self, const mcu_pin_obj_t *clock, const mcu_pin_obj_t *mosi, const mcu_pin_obj_t *miso, bool half_duplex) { - mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_SPI); + if (half_duplex) { + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_half_duplex); + } + + const struct device *dev = NULL; + int ret = iobroker_spi_allocate(clock->package_pin, + mosi != NULL ? mosi->package_pin : IOBROKER_NO_PIN, + miso != NULL ? miso->package_pin : IOBROKER_NO_PIN, &dev); + if (ret < 0) { + if (ret == -ENODEV) { + mp_raise_ValueError(MP_ERROR_TEXT("All SPI peripherals are in use")); + } + if (ret == -EBUSY) { + mp_raise_ValueError(MP_ERROR_TEXT("Internal resource(s) in use")); + } + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_SPI); + } + + common_hal_busio_spi_construct_from_device(self, dev); + self->dynamic = true; + self->clock = clock; + self->mosi = mosi; + self->miso = miso; + + // Initialize the deferred device now that it is routed to the requested + // pins. Fixed devicetree instances are already initialized (-EALREADY). + int init_ret = device_init(dev); + if (init_ret < 0 && init_ret != -EALREADY) { + // The failed init may have routed pins and left the device in a + // partial state; deinit gives up the claim and resets the pins. + common_hal_busio_spi_deinit(self); + raise_zephyr_error(init_ret); + } } bool common_hal_busio_spi_deinited(busio_spi_obj_t *self) { - // Always leave it active - return false; + return self->spi_device == NULL; } void common_hal_busio_spi_deinit(busio_spi_obj_t *self) { if (common_hal_busio_spi_deinited(self)) { return; } - // Always leave it active + if (self->dynamic) { + // The release de-inits the device, which applies its low-power + // pinctrl state and leaves the routed pins disconnected. + (void)iobroker_release(self->spi_device); + self->clock = NULL; + self->mosi = NULL; + self->miso = NULL; + self->spi_device = NULL; + } } void common_hal_busio_spi_mark_deinit(busio_spi_obj_t *self) { - // Not needed for Zephyr port + if (self->dynamic) { + self->spi_device = NULL; + } } bool common_hal_busio_spi_try_lock(busio_spi_obj_t *self) { diff --git a/ports/zephyr-cp/common-hal/busio/SPI.h b/ports/zephyr-cp/common-hal/busio/SPI.h index 87411c9825c..57641c173ac 100644 --- a/ports/zephyr-cp/common-hal/busio/SPI.h +++ b/ports/zephyr-cp/common-hal/busio/SPI.h @@ -7,6 +7,8 @@ #pragma once #include "py/obj.h" + +#include "common-hal/microcontroller/Pin.h" #include #include @@ -18,6 +20,12 @@ typedef struct { struct spi_config config[2]; // Two configs for pointer comparison by driver uint8_t active_config; // Index of currently active config (0 or 1) struct k_poll_signal signal; + // True when the underlying Zephyr device was dynamically routed to the + // pins below at construction time. + bool dynamic; + const mcu_pin_obj_t *clock; + const mcu_pin_obj_t *mosi; + const mcu_pin_obj_t *miso; } busio_spi_obj_t; // Helper function for Zephyr-specific initialization from device tree diff --git a/ports/zephyr-cp/common-hal/busio/UART.c b/ports/zephyr-cp/common-hal/busio/UART.c index af1de0e9023..b230cc7180e 100644 --- a/ports/zephyr-cp/common-hal/busio/UART.c +++ b/ports/zephyr-cp/common-hal/busio/UART.c @@ -6,17 +6,22 @@ #include "shared-bindings/microcontroller/__init__.h" #include "shared-bindings/busio/UART.h" +#include "shared-bindings/microcontroller/Pin.h" #include "shared/runtime/interrupt_char.h" #include "py/mpconfig.h" #include "py/gc.h" +#include "py/mphal.h" #include "py/mperrno.h" #include "py/runtime.h" #include "py/stream.h" +#include "bindings/zephyr_kernel/__init__.h" + #include #include +#include #include #include LOG_MODULE_REGISTER(busio_uart); @@ -57,6 +62,12 @@ void common_hal_busio_uart_never_reset(busio_uart_obj_t *self) { mp_obj_t common_hal_busio_uart_construct_from_device(busio_uart_obj_t *self, const struct device *uart_device, uint16_t receiver_buffer_size, byte *receiver_buffer) { self->base.type = &busio_uart_type; self->uart_device = uart_device; + self->dynamic = false; + self->receiver_buffer = NULL; + self->tx = NULL; + self->rx = NULL; + self->rts = NULL; + self->cts = NULL; int ret = uart_irq_callback_user_data_set(uart_device, serial_cb, self); if (ret < 0) { @@ -73,7 +84,8 @@ mp_obj_t common_hal_busio_uart_construct_from_device(busio_uart_obj_t *self, con return MP_OBJ_FROM_PTR(self); } -// Standard busio construct - not used in Zephyr port (devices come from device tree) +// Standard busio construct: pick a free peripheral instance and route it to +// the requested pins at runtime (supported on nRF SoCs). void common_hal_busio_uart_construct(busio_uart_obj_t *self, const mcu_pin_obj_t *tx, const mcu_pin_obj_t *rx, const mcu_pin_obj_t *rts, const mcu_pin_obj_t *cts, @@ -81,15 +93,102 @@ void common_hal_busio_uart_construct(busio_uart_obj_t *self, uint32_t baudrate, uint8_t bits, busio_uart_parity_t parity, uint8_t stop, mp_float_t timeout, uint16_t receiver_buffer_size, byte *receiver_buffer, bool sigint_enabled) { - mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_UART); + if (rs485_dir != NULL) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("RS485")); + } + // nRF UARTE only supports 8 data bits. + mp_arg_validate_int(bits, 8, MP_QSTR_bits); + + const struct device *dev = NULL; + int ret = iobroker_uart_allocate(tx != NULL ? tx->package_pin : IOBROKER_NO_PIN, + rx != NULL ? rx->package_pin : IOBROKER_NO_PIN, + rts != NULL ? rts->package_pin : IOBROKER_NO_PIN, + cts != NULL ? cts->package_pin : IOBROKER_NO_PIN, &dev); + if (ret < 0) { + if (ret == -ENODEV) { + mp_raise_ValueError(MP_ERROR_TEXT("All UART peripherals are in use")); + } + if (ret == -EBUSY) { + mp_raise_ValueError(MP_ERROR_TEXT("Internal resource(s) in use")); + } + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_UART); + } + + bool allocated_buffer = false; + if (receiver_buffer == NULL) { + receiver_buffer = m_malloc(receiver_buffer_size); + allocated_buffer = true; + } + + common_hal_busio_uart_construct_from_device(self, dev, receiver_buffer_size, receiver_buffer); + self->dynamic = true; + self->receiver_buffer = allocated_buffer ? receiver_buffer : NULL; + self->tx = tx; + self->rx = rx; + self->rts = rts; + self->cts = cts; + + // Initialize the deferred device now that it is routed to the requested + // pins. Fixed devicetree instances are already initialized (-EALREADY). + int init_ret = device_init(dev); + if (init_ret < 0 && init_ret != -EALREADY) { + // The failed init may have routed pins and left the device in a + // partial state; deinit gives up the claim and resets the pins. + common_hal_busio_uart_deinit(self); + raise_zephyr_error(init_ret); + } + + // Apply line configuration. + struct uart_config config = { + .baudrate = baudrate, + .data_bits = UART_CFG_DATA_BITS_8, + .parity = (parity == BUSIO_UART_PARITY_NONE) ? UART_CFG_PARITY_NONE : + ((parity == BUSIO_UART_PARITY_EVEN) ? UART_CFG_PARITY_EVEN : UART_CFG_PARITY_ODD), + .stop_bits = (stop == 1) ? UART_CFG_STOP_BITS_1 : UART_CFG_STOP_BITS_2, + .flow_ctrl = (rts != NULL && cts != NULL) ? UART_CFG_FLOW_CTRL_RTS_CTS : UART_CFG_FLOW_CTRL_NONE, + }; + int config_ret = uart_configure(self->uart_device, &config); + if (config_ret < 0) { + LOG_ERR("uart_configure failed: %d (baudrate=%u stop=%u flow=%u)", + config_ret, baudrate, stop, (rts != NULL && cts != NULL)); + common_hal_busio_uart_deinit(self); + raise_zephyr_error(config_ret); + } + + self->timeout = K_USEC((uint64_t)(timeout * 1000000)); } bool common_hal_busio_uart_deinited(busio_uart_obj_t *self) { - return !device_is_ready(self->uart_device); + return self->uart_device == NULL; } void common_hal_busio_uart_deinit(busio_uart_obj_t *self) { - // Leave it active (managed by Zephyr) + if (common_hal_busio_uart_deinited(self)) { + return; + } + if (self->dynamic) { + // The device may not be fully initialized: construct de-inits this + // object when device_init() fails partway through. Zephyr then + // reports it not ready (init_res != 0), so only poke the driver + // when it is really up and running. The iobroker claim and any + // routed pins are still given up below. + if (device_is_ready(self->uart_device)) { + uart_irq_rx_disable(self->uart_device); + uart_irq_callback_user_data_set(self->uart_device, NULL, NULL); + } + // The release de-inits the device, which applies its low-power + // pinctrl state and leaves the routed pins disconnected. + (void)iobroker_release(self->uart_device); + self->tx = NULL; + self->rx = NULL; + self->rts = NULL; + self->cts = NULL; + if (self->receiver_buffer != NULL) { + m_free(self->receiver_buffer); + self->receiver_buffer = NULL; + } + self->uart_device = NULL; + } } // Read characters. diff --git a/ports/zephyr-cp/common-hal/busio/UART.h b/ports/zephyr-cp/common-hal/busio/UART.h index be0b7ff83a9..369e2e2bf93 100644 --- a/ports/zephyr-cp/common-hal/busio/UART.h +++ b/ports/zephyr-cp/common-hal/busio/UART.h @@ -8,6 +8,8 @@ #include "py/obj.h" +#include "common-hal/microcontroller/Pin.h" + #include typedef struct { @@ -20,6 +22,16 @@ typedef struct { k_timeout_t write_timeout; bool rx_paused; // set by irq if no space in rbuf + + // True when the underlying Zephyr device was dynamically routed to the + // pins below at construction time. Such objects own their receiver + // buffer and deinitialize the device and release their pins. + bool dynamic; + byte *receiver_buffer; + const mcu_pin_obj_t *tx; + const mcu_pin_obj_t *rx; + const mcu_pin_obj_t *rts; + const mcu_pin_obj_t *cts; } busio_uart_obj_t; // Helper function for Zephyr-specific initialization from device tree diff --git a/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.c b/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.c index 0da16a9b720..cc2da9f5120 100644 --- a/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.c +++ b/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.c @@ -6,6 +6,8 @@ #include "shared-bindings/digitalio/DigitalInOut.h" +#include + #include #include @@ -15,15 +17,25 @@ void common_hal_digitalio_digitalinout_never_reset( digitalinout_result_t common_hal_digitalio_digitalinout_construct( digitalio_digitalinout_obj_t *self, const mcu_pin_obj_t *pin) { - claim_pin(pin); + // Claim the pin in the iobroker module so that bus allocations refuse + // it while this object holds it. The call also resolves the GPIO + // controller device and pin number from the pin's global number; they are + // kept in the object for every later pad operation. + int ret = iobroker_gpio_allocate(pin->package_pin, &self->port, &self->number); + if (ret < 0) { + return DIGITALINOUT_PIN_BUSY; + } + self->pin = pin; - if (!device_is_ready(pin->port)) { + if (!device_is_ready(self->port)) { printk("Port device not ready\n"); + common_hal_digitalio_digitalinout_deinit(self); return DIGITALINOUT_PIN_BUSY; } - if (gpio_pin_configure(pin->port, pin->number, GPIO_INPUT) != 0) { + if (gpio_pin_configure(self->port, self->number, GPIO_INPUT) != 0) { + common_hal_digitalio_digitalinout_deinit(self); return DIGITALINOUT_PIN_BUSY; } self->direction = DIRECTION_INPUT; @@ -40,7 +52,7 @@ void common_hal_digitalio_digitalinout_deinit(digitalio_digitalinout_obj_t *self return; } - reset_pin(self->pin); + (void)iobroker_gpio_release(self->port, self->number); self->pin = NULL; } @@ -68,7 +80,7 @@ digitalio_direction_t common_hal_digitalio_digitalinout_get_direction( void common_hal_digitalio_digitalinout_set_value( digitalio_digitalinout_obj_t *self, bool value) { - int res = gpio_pin_set(self->pin->port, self->pin->number, value); + int res = gpio_pin_set(self->port, self->number, value); if (res != 0) { printk("Failed to set value %d\n", res); } @@ -81,7 +93,7 @@ bool common_hal_digitalio_digitalinout_get_value( if (self->direction == DIRECTION_OUTPUT) { return self->value; } - return gpio_pin_get(self->pin->port, self->pin->number) == 1; + return gpio_pin_get(self->port, self->number) == 1; } digitalinout_result_t common_hal_digitalio_digitalinout_set_drive_mode( @@ -92,7 +104,7 @@ digitalinout_result_t common_hal_digitalio_digitalinout_set_drive_mode( if (drive_mode == DRIVE_MODE_OPEN_DRAIN) { flags |= GPIO_OPEN_DRAIN; } - int res = gpio_pin_configure(self->pin->port, self->pin->number, flags); + int res = gpio_pin_configure(self->port, self->number, flags); if (res != 0) { // TODO: Fake open drain. printk("Failed to set drive mode %d\n", res); @@ -115,7 +127,7 @@ digitalinout_result_t common_hal_digitalio_digitalinout_set_pull( } else if (pull == PULL_DOWN) { pull_flags = GPIO_PULL_DOWN; } - if (gpio_pin_configure(self->pin->port, self->pin->number, GPIO_INPUT | pull_flags) != 0) { + if (gpio_pin_configure(self->port, self->number, GPIO_INPUT | pull_flags) != 0) { return DIGITALINOUT_INVALID_PULL; } self->pull = pull; diff --git a/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.h b/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.h index 9e0c3265268..4d21bb46a10 100644 --- a/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.h +++ b/ports/zephyr-cp/common-hal/digitalio/DigitalInOut.h @@ -14,6 +14,10 @@ typedef struct { mp_obj_base_t base; const mcu_pin_obj_t *pin; + // GPIO controller device and pin number within it, resolved from the + // pin's global number by the gpio allocate call at construct time. + const struct device *port; + gpio_pin_t number; digitalio_direction_t direction; bool value; digitalio_drive_mode_t drive_mode; diff --git a/ports/zephyr-cp/common-hal/microcontroller/Pin.c b/ports/zephyr-cp/common-hal/microcontroller/Pin.c index 66882b6b5f0..c5832c13505 100644 --- a/ports/zephyr-cp/common-hal/microcontroller/Pin.c +++ b/ports/zephyr-cp/common-hal/microcontroller/Pin.c @@ -5,69 +5,67 @@ // SPDX-License-Identifier: MIT #include "shared-bindings/microcontroller/Pin.h" -#include "shared-bindings/digitalio/DigitalInOut.h" #include "py/mphal.h" -// Bit mask of claimed pins on each of up to two ports. nrf52832 has one port; nrf52840 has two. -// static uint32_t claimed_pins[GPIO_COUNT]; -// static uint32_t never_reset_pins[GPIO_COUNT]; +#include -void reset_all_pins(void) { - // for (size_t i = 0; i < GPIO_COUNT; i++) { - // claimed_pins[i] = never_reset_pins[i]; - // } - - // for (uint32_t pin = 0; pin < NUMBER_OF_PINS; ++pin) { - // if ((never_reset_pins[nrf_pin_port(pin)] & (1 << nrf_relative_pin_number(pin))) != 0) { - // continue; - // } - // nrf_gpio_cfg_default(pin); - // } - - // // After configuring SWD because it may be shared. - // reset_speaker_enable_pin(); -} - -// Mark pin as free and return it to a quiescent state. -void reset_pin(const mcu_pin_obj_t *pin) { +// Pin claims and pad reset live in the iobroker module: the objects that use +// a pin (busio buses through iobroker_*_allocate(), digitalio and rotaryio +// through iobroker_gpio_allocate()) own the claim and give it up again when +// they deinit, so the port keeps no claim table of its own. +// iobroker_pin_in_use() answers whether a pin is taken. - // Clear claimed bit. - // claimed_pins[nrf_pin_port(pin_number)] &= ~(1 << nrf_relative_pin_number(pin_number)); - // never_reset_pins[nrf_pin_port(pin_number)] &= ~(1 << nrf_relative_pin_number(pin_number)); +void reset_all_pins(void) { + // Nothing to do: pins belong to the objects that allocated them through + // iobroker, and those release their claims (and reset the pads) when they + // deinit. } - void never_reset_pin_number(uint8_t pin_number) { - // never_reset_pins[nrf_pin_port(pin_number)] |= 1 << nrf_relative_pin_number(pin_number); + // Deprecated single-byte pin number API; not used by this port. + (void)pin_number; } void common_hal_never_reset_pin(const mcu_pin_obj_t *pin) { - never_reset_pin_number(pin->number); + // Nothing to mark: reset_all_pins() leaves pins alone, so there is no + // reset to opt out of. + (void)pin; } void common_hal_reset_pin(const mcu_pin_obj_t *pin) { - if (pin == NULL) { - return; - } - reset_pin(pin); + // iobroker resets the pads when the object holding them releases its + // claim; a pin on its own has nothing to reset here. + (void)pin; } -void claim_pin(const mcu_pin_obj_t *pin) { - // Set bit in claimed_pins bitmask. - // claimed_pins[nrf_pin_port(pin->number)] |= 1 << nrf_relative_pin_number(pin->number); +bool pin_number_is_free(uint8_t pin_number) { + // Deprecated single-byte pin number API; not used by this port. + (void)pin_number; + return true; } +bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { + if (pin == NULL) { + return true; + } + return !iobroker_pin_in_use(pin->package_pin); +} -bool pin_number_is_free(uint8_t pin_number) { - return false; // !(claimed_pins[nrf_pin_port(pin_number)] & (1 << nrf_relative_pin_number(pin_number))); +void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin) { + // iobroker records the claim when the object using the pin allocates it; + // a bare claim has nothing to record and nobody to release it. + (void)pin; } -bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { - return true; +uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin) { + return (uint8_t)pin->number; +} +void common_hal_mcu_pin_claim_number(uint8_t pin_no) { + (void)pin_no; } -void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin) { - claim_pin(pin); +void common_hal_mcu_pin_reset_number(uint8_t pin_no) { + (void)pin_no; } diff --git a/ports/zephyr-cp/common-hal/microcontroller/Pin.h b/ports/zephyr-cp/common-hal/microcontroller/Pin.h index d38ab9bd200..86c2eb5a6af 100644 --- a/ports/zephyr-cp/common-hal/microcontroller/Pin.h +++ b/ports/zephyr-cp/common-hal/microcontroller/Pin.h @@ -9,16 +9,21 @@ #include "py/mphal.h" #include "py/obj.h" +#include #include typedef struct { mp_obj_base_t base; - const struct device *port; - gpio_pin_t number; + // Global pin number: gpio port index * 32 + pin within the port. The + // GPIO controller device and pin number within it are resolved from it + // when needed (iobroker_gpio_split()). + uint16_t number; + // Package pin of the SoC package the pad is bonded to, resolved at + // build time from the board's package pin map. IOBROKER_NO_PIN when the + // pad has no entry in the map (or the SoC has no package pin map). + package_pin_t package_pin; } mcu_pin_obj_t; #include "autogen-pins.h" void reset_all_pins(void); -void reset_pin(const mcu_pin_obj_t *pin); -void claim_pin(const mcu_pin_obj_t *pin); diff --git a/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.c b/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.c index d36b571535a..4901bbbe43b 100644 --- a/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.c +++ b/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.c @@ -12,6 +12,7 @@ #include "py/runtime.h" #include +#include #include #include #include @@ -27,8 +28,8 @@ static void incrementalencoder_gpio_callback(const struct device *port, return; } - int a = gpio_pin_get(self->pin_a->port, self->pin_a->number); - int b = gpio_pin_get(self->pin_b->port, self->pin_b->number); + int a = gpio_pin_get(self->port_a, self->number_a); + int b = gpio_pin_get(self->port_b, self->number_b); if (a < 0 || b < 0) { return; } @@ -45,18 +46,34 @@ void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencode self->pin_b = pin_b; self->divisor = 4; - if (!device_is_ready(pin_a->port) || !device_is_ready(pin_b->port)) { + // Claim both pins in the iobroker module so that bus allocations + // refuse them while this object holds them. The calls also resolve the + // GPIO controller devices and pin numbers from the pins' global numbers; + // they are kept in the object for every later pad operation. + int ret = iobroker_gpio_allocate(pin_a->package_pin, &self->port_a, &self->number_a); + if (ret < 0) { + common_hal_rotaryio_incrementalencoder_deinit(self); + raise_zephyr_error(ret); + } + + ret = iobroker_gpio_allocate(pin_b->package_pin, &self->port_b, &self->number_b); + if (ret < 0) { + common_hal_rotaryio_incrementalencoder_deinit(self); + raise_zephyr_error(ret); + } + + if (!device_is_ready(self->port_a) || !device_is_ready(self->port_b)) { common_hal_rotaryio_incrementalencoder_deinit(self); raise_zephyr_error(-ENODEV); } - int result = gpio_pin_configure(pin_a->port, pin_a->number, GPIO_INPUT | GPIO_PULL_UP); + int result = gpio_pin_configure(self->port_a, self->number_a, GPIO_INPUT | GPIO_PULL_UP); if (result != 0) { common_hal_rotaryio_incrementalencoder_deinit(self); raise_zephyr_error(result); } - result = gpio_pin_configure(pin_b->port, pin_b->number, GPIO_INPUT | GPIO_PULL_UP); + result = gpio_pin_configure(self->port_b, self->number_b, GPIO_INPUT | GPIO_PULL_UP); if (result != 0) { common_hal_rotaryio_incrementalencoder_deinit(self); raise_zephyr_error(result); @@ -64,8 +81,8 @@ void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencode self->callback_a.encoder = self; gpio_init_callback(&self->callback_a.callback, incrementalencoder_gpio_callback, - BIT(pin_a->number)); - result = gpio_add_callback(pin_a->port, &self->callback_a.callback); + BIT(self->number_a)); + result = gpio_add_callback(self->port_a, &self->callback_a.callback); if (result != 0) { common_hal_rotaryio_incrementalencoder_deinit(self); raise_zephyr_error(result); @@ -73,32 +90,29 @@ void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencode self->callback_b.encoder = self; gpio_init_callback(&self->callback_b.callback, incrementalencoder_gpio_callback, - BIT(pin_b->number)); - result = gpio_add_callback(pin_b->port, &self->callback_b.callback); + BIT(self->number_b)); + result = gpio_add_callback(self->port_b, &self->callback_b.callback); if (result != 0) { common_hal_rotaryio_incrementalencoder_deinit(self); raise_zephyr_error(result); } - result = gpio_pin_interrupt_configure(pin_a->port, pin_a->number, GPIO_INT_EDGE_BOTH); + result = gpio_pin_interrupt_configure(self->port_a, self->number_a, GPIO_INT_EDGE_BOTH); if (result != 0) { common_hal_rotaryio_incrementalencoder_deinit(self); raise_zephyr_error(result); } - result = gpio_pin_interrupt_configure(pin_b->port, pin_b->number, GPIO_INT_EDGE_BOTH); + result = gpio_pin_interrupt_configure(self->port_b, self->number_b, GPIO_INT_EDGE_BOTH); if (result != 0) { common_hal_rotaryio_incrementalencoder_deinit(self); raise_zephyr_error(result); } - int a = gpio_pin_get(pin_a->port, pin_a->number); - int b = gpio_pin_get(pin_b->port, pin_b->number); + int a = gpio_pin_get(self->port_a, self->number_a); + int b = gpio_pin_get(self->port_b, self->number_b); uint8_t quiescent_state = ((uint8_t)(a > 0) << 1) | (uint8_t)(b > 0); shared_module_softencoder_state_init(self, quiescent_state); - - claim_pin(pin_a); - claim_pin(pin_b); } bool common_hal_rotaryio_incrementalencoder_deinited(rotaryio_incrementalencoder_obj_t *self) { @@ -111,14 +125,22 @@ void common_hal_rotaryio_incrementalencoder_deinit(rotaryio_incrementalencoder_o } // Best-effort cleanup. During failed construct(), some of these may not be - // initialized yet. Ignore cleanup errors. - gpio_pin_interrupt_configure(self->pin_a->port, self->pin_a->number, GPIO_INT_DISABLE); - gpio_pin_interrupt_configure(self->pin_b->port, self->pin_b->number, GPIO_INT_DISABLE); - gpio_remove_callback(self->pin_a->port, &self->callback_a.callback); - gpio_remove_callback(self->pin_b->port, &self->callback_b.callback); - - reset_pin(self->pin_a); - reset_pin(self->pin_b); + // initialized yet. Ignore cleanup errors. The pad operations are only run + // when both claims were taken, so that the devices are valid. + if (self->port_a != NULL && self->port_b != NULL) { + gpio_pin_interrupt_configure(self->port_a, self->number_a, GPIO_INT_DISABLE); + gpio_pin_interrupt_configure(self->port_b, self->number_b, GPIO_INT_DISABLE); + gpio_remove_callback(self->port_a, &self->callback_a.callback); + gpio_remove_callback(self->port_b, &self->callback_b.callback); + } + if (self->port_a != NULL) { + (void)iobroker_gpio_release(self->port_a, self->number_a); + self->port_a = NULL; + } + if (self->port_b != NULL) { + (void)iobroker_gpio_release(self->port_b, self->number_b); + self->port_b = NULL; + } common_hal_rotaryio_incrementalencoder_mark_deinit(self); } @@ -126,4 +148,6 @@ void common_hal_rotaryio_incrementalencoder_deinit(rotaryio_incrementalencoder_o void common_hal_rotaryio_incrementalencoder_mark_deinit(rotaryio_incrementalencoder_obj_t *self) { self->pin_a = NULL; self->pin_b = NULL; + self->port_a = NULL; + self->port_b = NULL; } diff --git a/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.h b/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.h index a0d2bb392e2..55ab26c65d4 100644 --- a/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.h +++ b/ports/zephyr-cp/common-hal/rotaryio/IncrementalEncoder.h @@ -22,6 +22,12 @@ struct rotaryio_incrementalencoder_obj { mp_obj_base_t base; const mcu_pin_obj_t *pin_a; const mcu_pin_obj_t *pin_b; + // GPIO controller devices and pin numbers within them, resolved from the + // pins' global numbers by the gpio allocate calls at construct time. + const struct device *port_a; + gpio_pin_t number_a; + const struct device *port_b; + gpio_pin_t number_b; rotaryio_incrementalencoder_gpio_callback_t callback_a; rotaryio_incrementalencoder_gpio_callback_t callback_b; uint8_t state; // diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index 0a2a6ed0ae9..b2694cbb07a 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -114,7 +114,7 @@ # Other flags to set when a module is enabled EXTRA_FLAGS = { "audiobusio": {"AUDIOBUSIO_I2SOUT": 1, "AUDIOBUSIO_PDMIN": 0}, - "busio": {"BUSIO_SPI": 1, "BUSIO_I2C": 1}, + "busio": {"BUSIO_SPI": 1, "BUSIO_I2C": 1, "BUSIO_UART": 1}, "rotaryio": {"ROTARYIO_SOFTENCODER": 1}, "synthio": {"SYNTHIO_MAX_CHANNELS": 12}, } diff --git a/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py b/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py index b147ae0605e..988e55abff7 100644 --- a/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py +++ b/ports/zephyr-cp/cptools/tests/test_zephyr2cp.py @@ -15,7 +15,15 @@ sys.modules["cpbuild"] = type(sys)("cpbuild") sys.modules["cpbuild"].run_in_thread = lambda x: x -from zephyr2cp import find_flash_devices, find_ram_regions, BLOCKED_FLASH_COMPAT, MINIMUM_RAM_SIZE +import pytest + +from zephyr2cp import ( + find_flash_devices, + find_ram_regions, + BLOCKED_FLASH_COMPAT, + MINIMUM_RAM_SIZE, + add_toml_pin_names, +) def parse_dts_string(dts_content): @@ -587,3 +595,109 @@ def test_board_with_chosen_memory_region(self): assert len(rams) == 2 assert rams[0][0] == "axisram2" assert rams[1][0] == "axisram1" + + +class TestAddTomlPinNames: + """Test suite for add_toml_pin_names.""" + + def _add(self, toml_pins, package_pins, port_indexes, ioports): + board_names = {} + add_toml_pin_names( + board_names, {"pins": toml_pins}, package_pins, "nrf54l15_qfn48", port_indexes, ioports + ) + return board_names + + def test_package_pin_number(self): + package_pins = [ + {"pin": 1, "pad": 32, "pad_name": "P1.00"}, + {"pin": 5, "pad": 7, "pad_name": "P0.07"}, + ] + board_names = self._add({"LED": 5}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio0": {7}}) + assert board_names == {("gpio0", 7): ["LED"]} + + def test_ball_id(self): + package_pins = [{"pin": 12, "pad": 47, "pad_name": "P1.15", "ball": "B2"}] + board_names = self._add( + {"LED": "b2"}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio1": {15}} + ) + assert board_names == {("gpio1", 15): ["LED"]} + + def test_name_sanitized(self): + package_pins = [{"pin": 3, "pad": 0, "pad_name": "P0.00"}] + board_names = self._add({"boot button": 3}, package_pins, {"gpio0": 0}, {"gpio0": {0}}) + assert board_names == {("gpio0", 0): ["BOOT_BUTTON"]} + + def test_missing_package_map_raises(self): + with pytest.raises(RuntimeError, match="package pin map"): + add_toml_pin_names( + {}, {"pins": {"LED": 1}}, None, "custom", {"gpio0": 0}, {"gpio0": {0}} + ) + + def test_unknown_package_pin_raises(self): + package_pins = [{"pin": 1, "pad": 32, "pad_name": "P1.00"}] + with pytest.raises(RuntimeError, match="package pin 9 is not in"): + self._add({"LED": 9}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio0": {0}}) + + def test_unknown_ball_raises(self): + package_pins = [{"pin": 1, "pad": 32, "pad_name": "P1.00"}] + with pytest.raises(RuntimeError, match="ball Z9"): + self._add({"LED": "Z9"}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio0": {0}}) + + def test_bad_value_type_raises(self): + package_pins = [{"pin": 1, "pad": 32, "pad_name": "P1.00"}] + with pytest.raises(RuntimeError, match="package pin number"): + self._add({"LED": True}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio0": {0}}) + + def test_bad_name_raises(self): + package_pins = [{"pin": 1, "pad": 32, "pad_name": "P1.00"}] + with pytest.raises(RuntimeError, match="not usable"): + self._add({"1 LED": 1}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio0": {0}}) + + def test_pad_not_on_gpio_controller_raises(self): + package_pins = [{"pin": 1, "pad": 32, "pad_name": "P1.00"}] + with pytest.raises(RuntimeError, match="not on an enabled GPIO controller"): + self._add({"LED": 1}, package_pins, {"gpio0": 0, "gpio1": 1}, {"gpio0": {0}}) + + def test_no_pins_is_noop(self): + board_names = {} + add_toml_pin_names(board_names, {}, None, None, {"gpio0": 0}, {"gpio0": {0}}) + assert board_names == {} + + +class TestAddTomlPinNamesDuplicates: + """Deduplication and conflict behavior of add_toml_pin_names.""" + + PACKAGE_PINS = [ + {"pin": 5, "pad": 36, "pad_name": "P1.04"}, + {"pin": 27, "pad": 2, "pad_name": "P0.02"}, + ] + PORT_INDEXES = {"gpio0": 0, "gpio1": 1} + IOPORTS = {"gpio0": {2}, "gpio1": {4}} + + def _add(self, toml_pins, board_names=None): + board_names = dict(board_names or {}) + add_toml_pin_names( + board_names, + {"pins": toml_pins}, + self.PACKAGE_PINS, + "nrf54l15_qfn48", + self.PORT_INDEXES, + self.IOPORTS, + ) + return board_names + + def test_dedupes_two_keys_same_name_same_pin(self): + board_names = self._add({"MY PIN": 5, "my-pin": 5}) + assert board_names == {("gpio1", 4): ["MY_PIN"]} + + def test_conflict_two_keys_same_name_different_pins(self): + with pytest.raises(RuntimeError, match="already maps to gpio1 pin 4"): + self._add({"MY PIN": 5, "my-pin": 27}) + + def test_dedupes_existing_devicetree_name_same_pin(self): + board_names = self._add({"green led": 5}, {("gpio1", 4): ["Green LED"]}) + assert board_names == {("gpio1", 4): ["Green LED"]} + + def test_conflict_existing_devicetree_name_different_pin(self): + with pytest.raises(RuntimeError, match="already maps to gpio0 pin 2"): + self._add({"STATUS": 5}, {("gpio0", 2): ["STATUS"]}) diff --git a/ports/zephyr-cp/cptools/zephyr2cp.py b/ports/zephyr-cp/cptools/zephyr2cp.py index cc40ccfda85..f58f4ce75b2 100644 --- a/ports/zephyr-cp/cptools/zephyr2cp.py +++ b/ports/zephyr-cp/cptools/zephyr2cp.py @@ -1,6 +1,7 @@ import logging import pathlib import re +import tomllib import cpbuild import yaml @@ -487,6 +488,37 @@ def find_ram_regions(device_tree): INPUT_KEY_NAMES = {} +# Mask selecting the nRF pin number field (absolute pin, port*32+pin) of +# a pinctrl psel entry. The pin control entry uses all-ones in this field to +# mark a disconnected signal (NRF_PIN_DISCONNECTED). +NRF_PIN_FIELD_MASK = 0x1FF + + +def _pinctrl_default_psels(node): + """Return the raw nRF psel entries of a node's "default" pinctrl state. + + The state node (referenced by pinctrl-0) groups its configuration in + child nodes (typically named group1, group2, ...) that each carry a + psels property. + + Returns None when the node does not use pinctrl. + """ + prop = node.props.get("pinctrl-0") + if prop is None: + return None + psels = [] + try: + for state in prop.to_nodes(): + for group in state.nodes.values(): + if "psels" not in group.props: + continue + for value in group.props["psels"].to_nums(): + psels.append(value) + except (dtlib.DTError, KeyError): + return None + return psels + + def _populate_input_key_names(): header = ( pathlib.Path(__file__).parent.parent @@ -509,6 +541,107 @@ def _populate_input_key_names(): _populate_input_key_names() +def add_toml_pin_names( + board_names, mpconfigboard, package_pins, package_choice, port_indexes, ioports +): + """Add board pin names from circuitpython.toml's ``[pins]`` table. + + Each entry maps a board module name to a package pin number or, for ball + grid array packages, the datasheet's ball id (e.g. ``"A1"``). Entries are + resolved to SoC pads with the iobroker package pin map selected by the + ``IOBROKER_PACKAGE`` choice, and appended to ``board_names`` like + devicetree-derived names are. + + A name that already maps to the same pin (from the devicetree walk or an + earlier entry) is deduplicated; a name that would map to a different pin + than an existing entry is a build error. + """ + toml_pins = (mpconfigboard or {}).get("pins") + if not toml_pins: + return + if package_pins is None: + reason = "NONE" if package_choice == "none" else "unset" + raise RuntimeError( + f"circuitpython.toml [pins] needs an iobroker package pin map but " + f"CONFIG_IOBROKER_PACKAGE is {reason}" + ) + pad_of_package_pin = {} + pad_of_ball = {} + for package_pin_entry in package_pins: + pin = package_pin_entry["pin"] + if "pad" not in package_pin_entry: + # Unbonded or non-GPIO ball; not usable for pin names. + continue + pad = package_pin_entry["pad"] + if pin in pad_of_package_pin and pad_of_package_pin[pin] != pad: + raise RuntimeError(f"package pin {pin} maps to multiple pads in the package pin map") + pad_of_package_pin[pin] = pad + if "ball" in package_pin_entry: + ball = package_pin_entry["ball"] + if ball in pad_of_ball and pad_of_ball[ball] != pad: + raise RuntimeError(f"ball {ball} maps to multiple pads in the package pin map") + pad_of_ball[ball] = pad + port_label_of_index = {index: label for label, index in port_indexes.items()} + + def sanitize(name): + return name.upper().replace(" ", "_").replace("-", "_").replace("(", "").replace(")", "") + + # Names already in use from the devicetree walk, as sanitized name -> + # list of pins it is attached to. + pins_of_name = {} + for pin_key, names in board_names.items(): + for existing_name in names: + pins = pins_of_name.setdefault(sanitize(existing_name), []) + if pin_key not in pins: + pins.append(pin_key) + for name, package_pin in toml_pins.items(): + board_name = sanitize(name) + if not re.match(r"^[A-Z][A-Z0-9_]*$", board_name): + raise RuntimeError( + f"circuitpython.toml [pins] name {name!r} is not usable as a board module name" + ) + if isinstance(package_pin, str): + ball = package_pin.strip().upper() + if ball not in pad_of_ball: + raise RuntimeError( + f"circuitpython.toml [pins] name {name}: ball {package_pin} is not in " + f"the package pin map" + ) + pad = pad_of_ball[ball] + elif isinstance(package_pin, int) and not isinstance(package_pin, bool): + if package_pin not in pad_of_package_pin: + raise RuntimeError( + f"circuitpython.toml [pins] name {name}: package pin {package_pin} is " + f"not in the package pin map" + ) + pad = pad_of_package_pin[package_pin] + else: + raise RuntimeError( + f"circuitpython.toml [pins] name {name}: value must be a package pin number " + f"or ball id" + ) + label = port_label_of_index.get(pad // 32) + if label not in ioports: + raise RuntimeError( + f"circuitpython.toml [pins] name {name}: package pin {package_pin} is on " + f"pad {pad}, which is not on an enabled GPIO controller" + ) + pin_key = (label, pad % 32) + previous_pins = pins_of_name.get(board_name, []) + if previous_pins and pin_key not in previous_pins: + previous = ", ".join(f"{label} pin {num}" for label, num in previous_pins) + raise RuntimeError( + f"circuitpython.toml [pins] name {name}: already maps to {previous}, " + f"but [pins] assigns it to {label} pin {pad % 32}" + ) + if previous_pins: + # Same name already attached to this pin (devicetree or an + # earlier entry); nothing to add. + continue + pins_of_name[board_name] = [pin_key] + board_names.setdefault(pin_key, []).append(board_name) + + @cpbuild.run_in_thread def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfigboard=None): # noqa: C901 board_dir = builddir / "board" @@ -693,7 +826,21 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig if status == "okay": ioports[node.labels[0]] = set(range(0, ngpios)) if gpio_map and compatible and compatible[0] != "gpio-nexus": - connector_pins = CONNECTORS.get(compatible[0], None) + # Per-board connector names from circuitpython.toml's + # ``[connectors.]`` table take precedence. They key the + # gpio-map position (the header pin number as a string) to a name + # or a list of names, so boards whose silkscreen differs from the + # generic per-compatible list can supply their own. + connector_override = None + if node.labels: + connector_override = ( + (mpconfigboard or {}).get("connectors", {}).get(node.labels[0]) + ) + connector_pins = ( + connector_override + if connector_override is not None + else CONNECTORS.get(compatible[0], None) + ) if connector_pins is None: logger.warning(f"Unsupported connector mapping compatible: {compatible[0]}") else: @@ -701,16 +848,25 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig for offset, t, label in gpio_map._markers: if not label: continue - if i >= len(connector_pins): - logger.warning( - f"Connector mapping for {compatible[0]} has more pins than names; " - f"stopping at {len(connector_pins)}" - ) - break num = int.from_bytes(gpio_map.value[offset + 4 : offset + 8], "big") + if isinstance(connector_pins, dict): + pin_entry = connector_pins.get(str(i)) + if pin_entry is None: + logger.debug( + f"Connector {node.labels[0]} position {i} has no name; skipping" + ) + i += 1 + continue + else: + if i >= len(connector_pins): + logger.warning( + f"Connector mapping for {compatible[0]} has more pins than names; " + f"stopping at {len(connector_pins)}" + ) + break + pin_entry = connector_pins[i] if (label, num) not in board_names: board_names[(label, num)] = [] - pin_entry = connector_pins[i] if isinstance(pin_entry, list): board_names[(label, num)].extend(pin_entry) else: @@ -787,6 +943,51 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig pin_declarations = ["#pragma once"] mcu_pin_mapping = [] board_pin_mapping = [] + # Hardware port index of each GPIO controller; it defines the global pin + # numbering (index * 32 + pin) that pin objects and the iobroker module + # both use. On nRF SoCs the index comes from the label digits (gpio0 -> + # port 0, gpio6 -> port 6). + port_indexes = {} + for label in sorted(ioports.keys()): + match = re.match(r"^gpio(\d+)$", label) + port_indexes[label] = int(match.group(1)) if match else len(port_indexes) + # Package pin map selected through the IOBROKER_PACKAGE choice: map each + # SoC pad to the package pin it is bonded to so that the pin objects can + # hand package pins straight to the iobroker module. When no package + # applies to the SoC (IOBROKER_PACKAGE_NONE) there is no map, so the pin + # objects get IOBROKER_NO_PIN instead. + package_pin_of_pad = {} + package_pins = None + package_choice = None + if config_present: + for line in config.read_text().splitlines(): + stripped = line.strip() + if not stripped.startswith("CONFIG_IOBROKER_PACKAGE_") or not stripped.endswith("=y"): + continue + package_choice = stripped[len("CONFIG_IOBROKER_PACKAGE_") : -len("=y")].lower() + if package_choice == "none": + continue + package_toml = ( + pathlib.Path(__file__).resolve().parent.parent + / "modules" + / "iobroker" + / "packages" + / f"{package_choice}.toml" + ) + with package_toml.open("rb") as f: + package = tomllib.load(f) + package_pins = package["pins"] + for package_pin_entry in package_pins: + if "pad" in package_pin_entry: + package_pin_of_pad[package_pin_entry["pad"]] = package_pin_entry["pin"] + break + # Board pin names from circuitpython.toml: ``[pins]`` maps a board module + # name to a package pin number or ball id, resolved to a SoC pad with the + # package pin map above. This is independent of Zephyr's devicetree + # labels and aliases. + add_toml_pin_names( + board_names, mpconfigboard, package_pins, package_choice, port_indexes, ioports + ) for ioport in sorted(ioports.keys()): for num in ioports[ioport]: pin_object_name = f"P{ioport[len(shared_prefix) :].upper()}_{num:02d}" @@ -794,8 +995,11 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig status_led = pin_object_name if boot_button and (ioport, num) == boot_button: boot_button = pin_object_name + global_number = port_indexes[ioport] * 32 + num + package_pin = package_pin_of_pad.get(global_number) + package_pin_init = str(package_pin) if package_pin is not None else "IOBROKER_NO_PIN" pin_defs.append( - f"const mcu_pin_obj_t pin_{pin_object_name} = {{ .base.type = &mcu_pin_type, .port = DEVICE_DT_GET(DT_NODELABEL({ioport})), .number = {num}}};" + f"const mcu_pin_obj_t pin_{pin_object_name} = {{ .base.type = &mcu_pin_type, .number = {global_number}, .package_pin = {package_pin_init}}};" ) pin_declarations.append(f"extern const mcu_pin_obj_t pin_{pin_object_name};") mcu_pin_mapping.append( @@ -820,6 +1024,19 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig board_pin_mapping = "\n ".join(board_pin_mapping) mcu_pin_mapping = "\n ".join(mcu_pin_mapping) + # Bus instances the board enabled with all-disconnected pins are routed to + # arbitrary pins at runtime by busio objects instead of being exposed as + # fixed board.X() singletons. + 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 + ): + iobroker_labels.add(labels[0]) + zephyr_binding_headers = [] zephyr_binding_objects = [] zephyr_binding_labels = [] @@ -857,6 +1074,10 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig if found_main: break for labels in instances: + if labels[0] in iobroker_labels: + # Dynamically routable instances are not exposed as board + # singletons; construct a busio object with pins instead. + continue instance_name = f"{driver.replace('/', '_')}_{labels[0]}" c_function_name = f"_{instance_name}" singleton_ptr = f"{c_function_name}_singleton" @@ -897,6 +1118,157 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig zephyr_binding_objects = "\n".join(zephyr_binding_objects) zephyr_binding_labels = "\n".join(zephyr_binding_labels) + # Generate tables of allocatable bus instances for the iobroker + # Zephyr module (dynamic pin routing; nRF SoCs). Instances enabled with + # all-disconnected pinctrl can be routed to arbitrary pins at runtime; + # instances with fixed devicetree pins are only usable when the requested + # pins match their state. + pinctrl_nrf = False + if config_present: + for line in config.read_text().splitlines(): + if line.startswith("CONFIG_PINCTRL_NRF="): + pinctrl_nrf = line.strip().endswith("=y") + break + + iobroker_includes = """ +#include +#include +""" + + iobroker_tables = "" + table_parts = [] + + # Map GPIO controller devices to their hardware port index. The indexes + # define the global pin numbering shared by the pin objects and the + # iobroker module, which resolves a global number back to the + # controller device and pin within it. Boards without GPIO controllers + # generate an empty table so that gpio_split() returns -EINVAL. + if ioports: + devices = ", ".join( + f"DEVICE_DT_GET(DT_NODELABEL({label}))" for label in sorted(ioports.keys()) + ) + indexes = ", ".join(str(port_indexes[label]) for label in sorted(ioports.keys())) + count = len(port_indexes) + else: + devices = "NULL" + indexes = "0" + count = 0 + table_parts.append( + f""" +const struct device * const iobroker_gpio_port_devices[] = {{ {devices} }}; +const uint8_t iobroker_gpio_port_indexes[] = {{ {indexes} }}; +const size_t iobroker_gpio_port_count = {count}; +""" + ) + + if pinctrl_nrf: + pool_kinds = (("i2c", "i2c"), ("spi", "spi"), ("serial", "uart")) + bus_table_parts = [] + for driver, pool in pool_kinds: + dynamic_entries = [] + fixed_entries = [] + for labels in active_zephyr_devices.get(driver, []): + node = device_tree.label2node[labels[0]] + if node in path2chosen: + # Console and other system devices are not allocatable. + continue + psels = _pinctrl_default_psels(node) + if psels is None or len(psels) > 4: + continue + if all((value & NRF_PIN_FIELD_MASK) == NRF_PIN_FIELD_MASK for value in psels): + dynamic_entries.append((labels[0], None, 0)) + else: + fixed_entries.append((labels[0], psels, len(psels))) + + entries = dynamic_entries + fixed_entries + + # Always define all three pools, even when empty: iobroker.c and + # the nRF routing code reference every pool's tables whenever + # CONFIG_PINCTRL_NRF is on, so an empty pool is still an empty + # array plus a zero count. + if not entries: + bus_table_parts.append( + "const iobroker_instance_t" + f" iobroker_{pool}_buses[] = {{}};\n" + "iobroker_state_t" + f" iobroker_{pool}_bus_states[ARRAY_SIZE(iobroker_{pool}_buses)];\n" + "const size_t" + f" iobroker_{pool}_bus_count = ARRAY_SIZE(iobroker_{pool}_buses);" + ) + continue + + entry_lines = [] + psel_arrays = [] + declares = [] + for label, psels, count in entries: + declares.append(f"PINCTRL_DT_DEV_CONFIG_DECLARE(DT_NODELABEL({label}));") + entry = ( + f" {{ .dev = DEVICE_DT_GET(DT_NODELABEL({label})), " + f".pcfg = PINCTRL_DT_DEV_CONFIG_GET(DT_NODELABEL({label}))" + ) + if psels is not None: + values = ", ".join(hex(value) for value in psels) + psel_arrays.append( + f"static const pinctrl_soc_pin_t cp_{label}_dt_psels[] = {{ {values} }};" + ) + entry += f", .dt_psels = cp_{label}_dt_psels, .dt_psel_count = {count}" + entry += " }," + entry_lines.append(entry) + + bus_table_parts.append( + "\n".join(declares) + + "\n\n" + + "\n".join(psel_arrays) + + f"\nconst iobroker_instance_t iobroker_{pool}_buses[] = {{\n" + + "\n".join(entry_lines) + + "\n};\n" + + f"iobroker_state_t iobroker_{pool}_bus_states[ARRAY_SIZE(iobroker_{pool}_buses)];\n" + + f"const size_t iobroker_{pool}_bus_count = ARRAY_SIZE(iobroker_{pool}_buses);" + ) + + iobroker_tables = ( + "#if defined(CONFIG_PINCTRL)\n" + "#include \n" + + "\n\n".join(bus_table_parts) + + "\n#endif // CONFIG_PINCTRL" + ) + + if pinctrl_nrf: + # Pads claimed at boot by fixed peripherals (console UART, flash + # instance, I2S, ...): their devicetree pinctrl "default" state points + # at real pads. iobroker reports these pads as always in use so that + # allocate() rejects requests for them with -EBUSY instead of + # re-routing pads that something else is already driving. Dynamically + # routable instances have all-disconnected default states and + # contribute nothing here. + reserved_pads = set() + for instances in active_zephyr_devices.values(): + for labels in instances: + node = device_tree.label2node[labels[0]] + psels = _pinctrl_default_psels(node) + if psels is None: + continue + for value in psels: + pad = value & NRF_PIN_FIELD_MASK + if pad != NRF_PIN_FIELD_MASK: + reserved_pads.add(pad) + if reserved_pads: + pads = ", ".join(str(pad) for pad in sorted(reserved_pads)) + reserved_table = ( + "const uint16_t iobroker_reserved_pads[] = { " + pads + " };\n" + "const size_t iobroker_reserved_pads_count = " + f"{len(reserved_pads)};" + ) + iobroker_tables = ( + "#if defined(CONFIG_PINCTRL)\n" + "#include \n" + + "\n\n".join(bus_table_parts + [reserved_table]) + + "\n#endif // CONFIG_PINCTRL" + ) + + if table_parts: + iobroker_tables = iobroker_tables + "\n\n" + "\n\n".join(table_parts) + # Generate i2sout_reset() that stops all board I2SOut instances if i2sout_instance_names: stop_calls = "\n ".join( @@ -1024,6 +1396,7 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig #include "py/mphal.h" {zephyr_binding_headers} +{iobroker_includes} {zephyr_display_header} const struct device* const flashes[] = {{ {", ".join(flashes)} }}; @@ -1038,6 +1411,7 @@ def zephyr_dts_to_cp_board(board_id, portdir, builddir, zephyrbuilddir, mpconfig {pin_defs} {zephyr_binding_objects} +{iobroker_tables} {zephyr_display_object} {i2sout_reset_func} diff --git a/ports/zephyr-cp/modules/iobroker/CMakeLists.txt b/ports/zephyr-cp/modules/iobroker/CMakeLists.txt new file mode 100644 index 00000000000..2e4f64a567d --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/CMakeLists.txt @@ -0,0 +1,84 @@ +# iobroker Zephyr module build. +# +# The core is always compiled so that callers (CircuitPython's common-hal +# code) can include and call the module directly on +# every SoC. SoC-specific routing implementations are added under +# src///; without one the allocate functions report -ENOSYS. + +zephyr_library() +# SoC-agnostic core: package pin resolution, GPIO claims, pin-in-use. +zephyr_library_sources(src/iobroker.c) + +# SoC implementations live in src///. The nRF implementation +# covers every nRF SoC (they share one pinctrl encoding), so the +# level is the nRF family. Other vendors add their own directories and a +# corresponding zephyr_library_sources_ifdef() line here. +zephyr_library_sources_ifdef(CONFIG_PINCTRL_NRF + src/nordic/nrf/iobroker_route.c +) +# Private headers shared by the core and the vendor implementations. +zephyr_library_include_directories(src) + +# Package pin map: render the TOML selected through the IOBROKER_PACKAGE +# choice into a build-directory translation unit. Kconfig has already run by +# the time module CMakeLists are included, so the CONFIG_ symbols are final +# here. When no reference map applies to the SoC, an empty map is generated +# instead, so package pin lookups fail cleanly with -EINVAL rather than the +# build failing to link. +set(IOBROKER_PACKAGE_CONFIGS + IOBROKER_PACKAGE_NRF54L15_QFN48 + IOBROKER_PACKAGE_NRF54LM20_CSP98 + IOBROKER_PACKAGE_NRF5340_QKAA + IOBROKER_PACKAGE_MDBT50Q_1MV2 + IOBROKER_PACKAGE_NRF52840_AQFN73 + IOBROKER_PACKAGE_RP2040_QFN56 +) +set(IOBROKER_PACKAGE_TOMLS + nrf54l15_qfn48 + nrf54lm20_csp98 + nrf5340_qkaa + mdbt50q_1mv2 + nrf52840_aqfn73 + rp2040_qfn56 +) +set(pkg_selected) +set(pkg_name) +foreach(pkg toml IN ZIP_LISTS IOBROKER_PACKAGE_CONFIGS IOBROKER_PACKAGE_TOMLS) + if(CONFIG_${pkg}) + set(pkg_selected ${pkg}) + set(pkg_name ${toml}) + endif() +endforeach() + +if(pkg_selected) + set(pkg_toml_file ${CMAKE_CURRENT_LIST_DIR}/packages/${pkg_name}.toml) + set(pkg_c_file ${CMAKE_CURRENT_BINARY_DIR}/package_pins_${pkg_name}.c) + add_custom_command(OUTPUT ${pkg_c_file} + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/tools/gen_package_c.py + ${pkg_toml_file} ${pkg_c_file} + DEPENDS ${pkg_toml_file} ${CMAKE_CURRENT_LIST_DIR}/tools/gen_package_c.py + COMMENT "iobroker: generating package pin map ${pkg_name}" + ) + set_source_files_properties(${pkg_c_file} PROPERTIES GENERATED TRUE) +else() + # No package map applies to this SoC (yet): an empty map keeps the symbols + # defined so the core can report -EINVAL for every package pin lookup. + set(pkg_c_file ${CMAKE_CURRENT_BINARY_DIR}/package_pins_empty.c) + file(WRITE ${pkg_c_file} + "// Generated by the iobroker module build -- no package pin map for this SoC.\n" + "#include \n" + "const iobroker_package_pin_t iobroker_package_pins[1] = {{0}};\n" + "const size_t iobroker_package_pin_count = 0;\n" + ) +endif() + +# The generated file is consumed by the zephyr target from the same +# directory, so order the compilation explicitly through a custom target. +add_custom_target(iobroker_package_pins DEPENDS ${pkg_c_file}) +add_dependencies(zephyr iobroker_package_pins) +target_sources(zephyr PRIVATE ${pkg_c_file}) + +# Register the public headers app-wide (zephyr_interface) so that external +# builds that consume Zephyr's exported compile flags, such as the +# CircuitPython static library build, can include them too. +zephyr_include_directories(include) diff --git a/ports/zephyr-cp/modules/iobroker/Kconfig b/ports/zephyr-cp/modules/iobroker/Kconfig new file mode 100644 index 00000000000..07e9bae8b07 --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/Kconfig @@ -0,0 +1,36 @@ +# iobroker: dynamic peripheral allocation and runtime pin routing. +# +# The module's core is always compiled (see CMakeLists.txt); callers call it +# directly. Runtime routing needs SoC support: today it is implemented for +# nRF SoCs, whose pin control encoding can be computed at runtime and whose +# peripherals can be routed to (almost) any pin via PSEL. On other SoCs the +# allocate functions always report -ENOSYS. + +config IOBROKER_GPIO_MAX_PINS + int "Maximum number of pins allocatable for GPIO use" + default 32 + help + Size of the registry of pins claimed for plain GPIO use via + iobroker_gpio_allocate(). + +choice IOBROKER_PACKAGE + prompt "SoC package pin map" + help + The module resolves requested package pins to SoC pads through the + package pin map selected here. Reference maps shipped with the module + live in packages/; when none applies to the SoC, the NONE option keeps + the map empty and every package pin lookup fails with -EINVAL. + +config IOBROKER_PACKAGE_NONE + bool "None (package pin lookups fail)" + help + No package pin map is available for this SoC yet. Package pin + lookups (and therefore pin claims and bus pin routing) fail with + -EINVAL until a reference map is added for the SoC. + +# Reference maps (one option per package transcribed from a SoC datasheet) +# live in Kconfig.packages; each depends on the SOC_* symbols of the SoCs the +# map applies to, so only the options valid for the selected SoC are visible. +rsource "Kconfig.packages" + +endchoice diff --git a/ports/zephyr-cp/modules/iobroker/Kconfig.packages b/ports/zephyr-cp/modules/iobroker/Kconfig.packages new file mode 100644 index 00000000000..a4b55bd4f8c --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/Kconfig.packages @@ -0,0 +1,71 @@ +# Reference package pin maps for the IOBROKER_PACKAGE choice. +# +# Generated by hand from the transcriptions in packages/*.toml (one option per +# TOML, depends on the SOC_* symbols of the SoCs the map applies to). When a +# package option is invisible the SoC does not come in that package, so the +# option cannot be selected or set from conf fragments either. +# +# Choice defaults live here too, most-specific first: the development kits' +# packages preselect the reference map for their SoC; boards with more than +# one matching package (e.g. nRF52840 modules) set their choice explicitly in +# board.conf; everything else falls back to NONE so package pin lookups fail +# cleanly. + +config IOBROKER_PACKAGE_NRF54L15_QFN48 + bool "nRF54L15/10/05 QFN48 (QFAA)" + depends on SOC_NRF54L15 || SOC_NRF54L10 || SOC_NRF54L05 + help + QFN48 package pin map for the nRF54L15, nRF54L10 and nRF54L05, which + are pin-compatible. 48 pins, 31 routable GPIOs. Used by the + nRF54L15 DK. + +config IOBROKER_PACKAGE_NRF54LM20_CSP98 + bool "nRF54LM20 CSP98 (PAAA)" + depends on SOC_NRF54LM20A || SOC_NRF54LM20B + help + CSP98 (flip-chip) package pin map for the nRF54LM20A and nRF54LM20B. + 98 balls, 66 routable GPIOs, numbered sequentially in row-major order + (the datasheet labels them A1..K10). Used by the nRF54LM20 DK. + +config IOBROKER_PACKAGE_NRF5340_QKAA + bool "nRF5340 aQFN94 (QKAA)" + depends on SOC_NRF5340_CPUAPP + help + aQFN94 package pin map for the nRF5340 application core. 94 balls + (plus four corner pads and a die pad, not in the map), all 48 GPIOs + bonded, numbered sequentially in row-major order (the datasheet only + labels the balls, e.g. A17). Used by the nRF5340 DK and the nRF7002 DK. + +config IOBROKER_PACKAGE_MDBT50Q_1MV2 + bool "Raytac MDBT50Q-1MV2 module" + depends on SOC_NRF52840_QIAA + help + Pin map for the Raytac MDBT50Q-1MV2 module, which carries the + nRF52840 aQFN73 inside. 'pin' numbers the module's 61 castellated + pins as the datasheet numbers them. + +config IOBROKER_PACKAGE_NRF52840_AQFN73 + bool "nRF52840 aQFN73 (NQI73)" + depends on SOC_NRF52840_QIAA + help + aQFN73 package pin map for the nRF52840. 73 balls, all 48 GPIOs + bonded, numbered sequentially in row-major order (the datasheet only + labels the balls, e.g. A8). + +config IOBROKER_PACKAGE_RP2040_QFN56 + bool "RP2040 QFN-56" + depends on SOC_RP2040 + help + QFN-56 package pin map for the RP2040. 30 user GPIOs (the package + pin map covers GPIO0..GPIO29; QSPI and power pins are omitted), + numbered as the datasheet numbers the pins. Used by the Feather + RP2040 board. + +choice IOBROKER_PACKAGE + default IOBROKER_PACKAGE_NRF54L15_QFN48 if SOC_NRF54L15 || SOC_NRF54L10 || SOC_NRF54L05 + default IOBROKER_PACKAGE_NRF54LM20_CSP98 if SOC_NRF54LM20A || SOC_NRF54LM20B + default IOBROKER_PACKAGE_NRF5340_QKAA if SOC_NRF5340_CPUAPP_QKAA + default IOBROKER_PACKAGE_NRF52840_AQFN73 if SOC_NRF52840_QIAA + default IOBROKER_PACKAGE_RP2040_QFN56 if SOC_RP2040 + default IOBROKER_PACKAGE_NONE +endchoice diff --git a/ports/zephyr-cp/modules/iobroker/README.md b/ports/zephyr-cp/modules/iobroker/README.md new file mode 100644 index 00000000000..0e360bb2ee0 --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/README.md @@ -0,0 +1,132 @@ +# iobroker + +A Zephyr module for **dynamic peripheral allocation and runtime pin routing**: +pick a free bus instance (I2C, SPI, UART) enabled in the devicetree, re-route +it to requested pins at runtime and hand the Zephyr device to the caller. + +Currently, runtime routing is implemented for nRF SoCs, whose pin control +encoding can be computed at runtime and whose peripherals can be routed to +(almost) any pin via PSEL. On other SoCs the module compiles but the allocate +functions always return `-ENOSYS`. + +## Source layout + +The source is organized by vendor and SoC family under `src/`: + +``` +src/ + iobroker.c # SoC-agnostic core: package pin map + # lookup, GPIO controller split, claim + # registry, -ENOSYS stubs when routing + # is unavailable + iobroker_internal.h # helpers shared between core and + # vendor implementations (private) + nordic/ + nrf/ # every nRF SoC shares one pinctrl + iobroker_route.c # encoding, so the family is one dir +``` + +The core compiles on every SoC. Each vendor adds a `src///` +directory with an implementation of the bus allocate/release functions and a +corresponding `zephyr_library_sources_ifdef()` line in `CMakeLists.txt`; +without one the core's `#if !IOBROKER_ROUTING` stubs report `-ENOSYS`. When +an implementation covers a whole SoC family (as the nRF one does, keyed on +`CONFIG_PINCTRL_NRF`), the directory is named for the family. + +## Enabling + +The module is registered by the CircuitPython Zephyr application +(`ports/zephyr-cp/CMakeLists.txt`) via `ZEPHYR_EXTRA_MODULES`, so it works +without west manifest changes. Its core is always compiled and its callers +include `` directly; runtime routing additionally +requires `CONFIG_PINCTRL_DYNAMIC` and `CONFIG_DEVICE_DEINIT_SUPPORT`, which +the port enables by default on nRF SoCs. + +## Board tables + +The core does not know which instances are allocatable; the application must +provide per-board tables (the generated `board.c` always emits them): + +```c +const iobroker_instance_t iobroker_i2c_buses[]; // + _states[] and _bus_count +const iobroker_instance_t iobroker_spi_buses[]; // ... +const iobroker_instance_t iobroker_uart_buses[]; // ... +const struct device * const iobroker_gpio_port_devices[]; // + _indexes[] and _count +const iobroker_package_pin_t iobroker_package_pins[]; // + _pin_count +const uint16_t iobroker_reserved_pads[]; // + _pin_count +``` + +The package pin map is selected from the module's reference maps: +`Kconfig.packages` offers one option per transcribed package +(`packages/*.toml`), each visible only for the SoCs it applies to and +preselected for the development kits. When no map applies to a SoC the +`IOBROKER_PACKAGE_NONE` choice is used and an empty map is generated, so +package pin lookups fail with `-EINVAL`. The selected TOML (or the empty +map) is rendered into a build-directory translation unit at build time. +New maps are transcribed from a SoC datasheet with `tools/gen_package.py` +(see the script's docstring; the datasheets live in `datasheets/`). + +Each instance entry contains the Zephyr device, its `struct +pinctrl_dev_config` (via `PINCTRL_DT_DEV_CONFIG_DECLARE`/`_GET`) and, when the +devicetree state has fixed pins, the raw `pinctrl_soc_pin_t` values of the +"default" state. Instances whose devicetree "default" state leaves every +signal disconnected (`NRF_PIN_DISCONNECTED`) set `.dt_psels = NULL` and can be +routed to any pin at runtime. Instances with fixed devicetree pins are only +allocatable when a request matches their existing state. + +In the CircuitPython tree these tables are generated into the board's +`board.c` by `cptools/zephyr2cp.py` at build time. + +`iobroker_reserved_pads` lists the SoC pads that fixed peripherals (console +UART, flash instance, I2S, ...) drive at boot through their devicetree +pinctrl default state. `iobroker_pin_in_use()` reports them as busy so that +allocate() rejects requests for those pads with `-EBUSY` instead of +re-routing pads something else is already driving. The table only contains +connected pins; dynamically routable instances (all signals disconnected in +the devicetree) contribute nothing. + +The GPIO controller table pairs each controller device with its hardware +port index. The indexes define the global pin numbering (index * 32 + pin +within the port) shared with the pin objects, and let the module resolve a +global pin number back to the controller device and pin number that +Zephyr's GPIO API takes. + +## Public API + +See `include/iobroker/iobroker.h`. Signals are requested with +`package_pin_t` values (`PACKAGE_PIN(n)`): the physical package pin numbered +as the SoC datasheet numbers it (QFN pins 1..N; CSP/BGA balls numbered +sequentially in row-major order, with the datasheet's ball label noted in the +map and the generated C). The module resolves package pins to SoC pads +through the package pin map, `iobroker_package_pins[]`, selected via +`CONFIG_IOBROKER_PACKAGE_*` (see above); internally the lookup then +proceeds package pin -> SoC pad -> peripheral routing. `IOBROKER_NO_PIN` leaves a +signal disconnected. Pull resistors are chosen by the module per bus signal +(I2C SDA/SCL and UART RX idle high). Every allocate call must be paired with +`iobroker_release()`, so the application owns the lifecycle. Releasing also +leaves the pins quiescent: `iobroker_release()` de-initializes the device, +which applies its low-power pinctrl state to the routed pins. +`iobroker_pin_in_use()` reports whether a package pin is claimed by a +currently allocated instance, and `allocate()` refuses (-EBUSY) requests that +would double-use a pin. Package pins can also be claimed for plain GPIO use +with `iobroker_gpio_allocate()` / `iobroker_gpio_release()`: the caller +configures the pad while the claim is held and the module returns it to a +quiescent state (disconnected) on release, and GPIO claims conflict with bus +allocations the same way bus allocations conflict with each other. +`iobroker_gpio_allocate()` resolves the package pin through the map +and returns both the GPIO controller device and the pin number within it. +`iobroker_gpio_package_pin()` maps a GPIO controller's hardware port index +and pin number (the two halves of the global pin numbering) back to the +package pin the pad is bonded to. + +## Externalizing + +This module is application-agnostic: it depends only on Zephyr. To move it +into its own repository: + +1. Copy this directory (minus this README) to the new repo root, keeping + `zephyr/module.yml`, `CMakeLists.txt`, `Kconfig`, `include/` and `src/`. +2. Point `ZEPHYR_EXTRA_MODULES` in `ports/zephyr-cp/CMakeLists.txt` at the new + checkout, or register it as a project in `zephyr-config/west.yml`. +3. Keep `iobroker.h`'s struct/function names stable — the generated + board tables are part of the module's de-facto API. diff --git a/ports/zephyr-cp/modules/iobroker/datasheets/README.md b/ports/zephyr-cp/modules/iobroker/datasheets/README.md new file mode 100644 index 00000000000..7bcec733a58 --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/datasheets/README.md @@ -0,0 +1,29 @@ +# Datasheets + +The package pin maps in `../packages/` are transcribed from Nordic's SoC +datasheets. The PDFs are not redistributed in this repository; download them +from Nordic (https://docs.nordicsemi.com) into this directory and verify them +against the SHA-256 hashes below before regenerating a map. + +| File | SHA-256 | +|------|---------| +| `nRF54L15_nRF54L10_nRF54L05_Datasheet_v1.0.pdf` | `ade0d340ba95e31f8299e6721b08d53a702962335d9b87fa202f8d2767603554` | +| `nRF54LM20A_nRF54LM20B_Datasheet_v1.0.pdf` | `03a834fb52be8cf6248df6f67737f055c035cfb83492d44d58bdc2f0bfa3a3af` | +| `nRF5340_PS_v1.6.pdf` | `fe5c7e8908c94080548a1ddd4ec8b96aa09a4b97239fe9b32afb49eb8cf2fc87` | +| `nRF52840_PS_v1.1.pdf` | `c619e336b9c0610663273041f057f2537a65fd408ce0c5b8214a26de2aa88422` | +| `[nRF52840] MDBT50Q-1MV2 & MDBT50Q-P1MV2_Ver.L spec.pdf` | `61fec8c0c9f8c33175be2237a8ebba73c6cfc0a3572fe3835fd341079c103d03` | +| `rp2040-datasheet.pdf` | `be56fbb75ba0ae9e26558a73c93ac3e75c2ad4e6878d3b6703de2a76d886ea8c` | + +The nRF52840 Product Specification is Nordic's, downloadable from +https://docs.nordicsemi.com; the copy here came from +https://cdn-learn.adafruit.com/assets/assets/000/092/427/original/nRF52840_PS_v1.1.pdf. +The MDBT50Q module datasheet is Raytac's, from +https://www.raytac.com. The RP2040 datasheet is Raspberry Pi's, from +https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf. + +The transcription command lines are recorded in the header of each +`packages/*.toml` (section number, package name, pin count). After running +`pdftotext -layout` on a verified PDF, the same command reproduces the map. + +This directory is ignored by git; this README is force-added so the hashes +stay with the code. diff --git a/ports/zephyr-cp/modules/iobroker/include/iobroker/iobroker.h b/ports/zephyr-cp/modules/iobroker/include/iobroker/iobroker.h new file mode 100644 index 00000000000..cdb268625bf --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/include/iobroker/iobroker.h @@ -0,0 +1,203 @@ +// iobroker: dynamic peripheral allocation and runtime pin routing. +// +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Adafruit Industries LLC +// +// SPDX-License-Identifier: MIT + +// Allocate a free bus instance (I2C, SPI, UART) enabled in the devicetree, +// route it to the requested pins at runtime and hand the Zephyr device to the +// caller. Per-board tables of allocatable instances are provided by the +// application (the CircuitPython build generates them from the board's +// devicetree; see modules/iobroker/README.md for the contract). +// +// Runtime routing works on nRF SoCs because their pin control encoding is a +// simple bit-packed value (see zephyr/dt-bindings/pinctrl/nrf-pinctrl.h and +// soc/nordic/common/pinctrl_soc.h) that can be computed at runtime, and any +// peripheral function can be routed to (almost) any pin via PSEL. + +#pragma once + +#include +#include +#include + +#include +#include + +// Runtime bus routing lets the module re-route a bus instance's pins at +// runtime. It is available only on nRF SoCs, and only when the nRF pinctrl +// driver is built with dynamic pinctrl states and device de-init support. +// Without it the bus allocate/release functions report -ENOSYS and the bus +// instance tables are not used. +#if defined(CONFIG_PINCTRL_NRF) && defined(CONFIG_PINCTRL_DYNAMIC) && \ + defined(CONFIG_DEVICE_DEINIT_SUPPORT) +#define IOBROKER_ROUTING 1 +#else +#define IOBROKER_ROUTING 0 +#endif + +// Signals needed by the widest bus (UART with tx/rx/rts/cts). +#define IOBROKER_MAX_PINS 4 + +// Package pin that leaves a signal disconnected (only valid for optional +// signals such as UART rts/cts). +#define IOBROKER_NO_PIN ((package_pin_t)0xffff) + +// A physical pin of the SoC's package, numbered the way the SoC datasheet +// numbers its package pins (1..N for QFN-style packages; BGA-style packages +// use the datasheet's sequential numbering of the balls). Translating a pin +// name from a schematic or board layout to a package pin is a board concern; +// the module only knows the package pin -> SoC pad mapping below. +typedef uint16_t package_pin_t; + +// Wrap a datasheet package pin number into a package_pin_t. +#define PACKAGE_PIN(number) ((package_pin_t)(number)) + +// One entry of the package pin map: a physical package pin and the SoC pad +// it is bonded to (gpio port index * 32 + pin within the port; the same +// numbering that CircuitPython's Pin objects and the GPIO controller table +// below use). Package pins that are not bonded to a GPIO (power, analog +// only, ...) are omitted; requesting one fails with -EINVAL. +// +// The map is generated by the module build from the selected IOBROKER_PACKAGE +// choice (see packages/), or as an empty map when no package applies to the +// SoC. With an empty map every package pin lookup fails with -EINVAL. +typedef struct { + package_pin_t package_pin; + uint16_t soc_pad; +} iobroker_package_pin_t; + +extern const iobroker_package_pin_t iobroker_package_pins[]; +extern const size_t iobroker_package_pin_count; + +// GPIO controller table: the board lists every GPIO controller together with +// its hardware port index, which defines the global pin numbering +// (index * 32 + pin). Generated into board.c; boards without GPIO +// controllers generate an empty table, so lookups return -EINVAL. +extern const struct device *const iobroker_gpio_port_devices[]; +extern const uint8_t iobroker_gpio_port_indexes[]; +extern const size_t iobroker_gpio_port_count; + +// Resolve a global pin number to its GPIO controller device and the pin +// number within that controller (the two values Zephyr's GPIO API takes). +// Returns 0, or -EINVAL when no controller covers the number. +int iobroker_gpio_split(uint16_t number, const struct device **port_out, + gpio_pin_t *pin_out); + +// Map a GPIO controller's hardware port index and the pin number within it +// (the two halves of the global pin numbering: index * 32 + pin) to the +// package pin the pad is bonded to. The inverse of the package pin map's +// soc_pad column. Returns 0, or -EINVAL when the pad has no entry in the +// package pin map. +int iobroker_gpio_package_pin(uint8_t port, gpio_pin_t pin, + package_pin_t *package_pin_out); + +#if defined(CONFIG_PINCTRL_NRF) + +#include + +// Description of one allocatable bus instance. Filled in by the board tables. +typedef struct { + const struct device *dev; + // Pin control configuration of the device. Mutable because + // CONFIG_PINCTRL_DYNAMIC moves these to RAM so that states can be + // swapped at runtime. + struct pinctrl_dev_config *pcfg; + // Pin control entries of the devicetree "default" state, or NULL when + // the instance was enabled with disconnected pins so that it can be + // routed dynamically to any pin. + const pinctrl_soc_pin_t *dt_psels; + uint8_t dt_psel_count; +} iobroker_instance_t; + +// Runtime bookkeeping for one allocatable bus instance. The arrays live for +// the life of the firmware because Zephyr's pinctrl API keeps pointers to +// them inside the device's pinctrl_dev_config. +typedef struct { + pinctrl_soc_pin_t default_pins[IOBROKER_MAX_PINS]; + pinctrl_soc_pin_t sleep_pins[IOBROKER_MAX_PINS]; + struct pinctrl_state states[2]; + bool in_use; + // True when the instance's states were swapped for runtime-built entries. + bool routed; + // Package pins the current allocation claims (copies of the request). + // Used by iobroker_pin_in_use(); empty while the instance is free. + package_pin_t pins[IOBROKER_MAX_PINS]; + uint8_t pin_count; +} iobroker_state_t; + +// Generated board tables, emitted by the board's generated board.c whenever +// the nRF pinctrl driver is built. They are used only when runtime routing is +// available (IOBROKER_ROUTING); boards without it still define them. +extern const iobroker_instance_t iobroker_i2c_buses[]; +extern const size_t iobroker_i2c_bus_count; +extern iobroker_state_t iobroker_i2c_bus_states[]; + +extern const iobroker_instance_t iobroker_spi_buses[]; +extern const size_t iobroker_spi_bus_count; +extern iobroker_state_t iobroker_spi_bus_states[]; + +extern const iobroker_instance_t iobroker_uart_buses[]; +extern const size_t iobroker_uart_bus_count; +extern iobroker_state_t iobroker_uart_bus_states[]; + +// SoC pads owned by fixed peripherals (console UART, flash instance, I2S, +// ...): the pads their devicetree pinctrl default state drives at boot. +// iobroker_pin_in_use() reports these as always busy so that allocate() +// rejects requests for them with -EBUSY. +extern const uint16_t iobroker_reserved_pads[]; +extern const size_t iobroker_reserved_pads_count; + +#endif // CONFIG_PINCTRL_NRF + +// The functions below return 0 on success and set *dev_out to the Zephyr +// device of an allocated instance. A negative errno is returned on failure: +// -ENODEV: no compatible instance is free +// -ENOSYS: dynamic pin routing is unsupported on this SoC +// -EBUSY: a requested pin is already claimed by an allocated instance +// -EINVAL/-EIO: a pin or routing operation failed +// Optional signals may be disconnected (IOBROKER_NO_PIN). Every allocate +// call must be paired with iobroker_release(). +int iobroker_i2c_allocate(package_pin_t sda, package_pin_t scl, + const struct device **dev_out); +int iobroker_spi_allocate(package_pin_t clock, package_pin_t mosi, + package_pin_t miso, const struct device **dev_out); +int iobroker_uart_allocate(package_pin_t tx, package_pin_t rx, + package_pin_t rts, package_pin_t cts, const struct device **dev_out); + +// Returns true when the package pin is currently claimed by an allocated bus +// instance, a GPIO allocation, or a fixed peripheral (console UART, flash +// instance, ... whose pads are listed in iobroker_reserved_pads). Call this +// before allocate() to find out which requested pin is already in use. +// Disconnected signals (IOBROKER_NO_PIN) and package pins with no SoC pad are +// never in use. +bool iobroker_pin_in_use(package_pin_t pin); + +// Allocate a package pin for plain GPIO use. The module only claims it: while +// held, bus allocate() calls and further GPIO allocations fail with -EBUSY. +// The pin is resolved through the package pin map, and both values Zephyr's +// GPIO API needs are returned: *port_out receives the GPIO controller device +// and *pin_out the pin number within it, so that the caller can configure the +// pad itself (gpio_pin_configure()). Returns 0, or a negative errno: +// -EINVAL: pin is disconnected (IOBROKER_NO_PIN) or has no entry in the +// package pin map +// -EBUSY: the pin is already claimed +// -ENOMEM: the GPIO claim registry is full +int iobroker_gpio_allocate(package_pin_t pin, + const struct device **port_out, gpio_pin_t *pin_out); + +// Release a GPIO claim made with iobroker_gpio_allocate() and return the pad +// to a quiescent state: disconnected from any peripheral routing, input +// buffer and driver off, no pulls (a plain input on SoCs without +// GPIO_DISCONNECTED support). Pass the device and pin number that the +// allocate call returned. Returns true when a claim was held. +bool iobroker_gpio_release(const struct device *port, gpio_pin_t number); + +// Release an instance previously returned by one of the allocate functions. +// Returns true when the instance had been dynamically routed, in which case +// the caller is responsible for resetting the pins it had routed to it. The +// device is de-initialized on release, so it is left uninitialized; the +// caller initializes it again when it allocates the instance next. +bool iobroker_release(const struct device *dev); diff --git a/ports/zephyr-cp/modules/iobroker/packages/mdbt50q_1mv2.toml b/ports/zephyr-cp/modules/iobroker/packages/mdbt50q_1mv2.toml new file mode 100644 index 00000000000..a7ea7b89ae7 --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/packages/mdbt50q_1mv2.toml @@ -0,0 +1,302 @@ +# Package pin map for the Raytac MDBT50Q-1MV2 module (nRF52840 aQFN73 +# inside), transcribed from "[nRF52840] MDBT50Q-1MV2 & MDBT50Q-P1MV2_Ver.L +# spec.pdf", section 2.5 Pin Assignment, with a one-off pdftotext -layout +# parse. Review against the datasheet figure before editing by hand. +# +# 'pin' numbers the module's castellated pins as the datasheet numbers them +# (1..61); non-GPIO pins carry no pad. + +name = "mdbt50q_1mv2" +socs = ["SOC_NRF52840_QIAA"] + +[[pins]] +pin = 1 +name = "GND" # non-GPIO + +[[pins]] +pin = 2 +name = "GND" # non-GPIO + +[[pins]] +pin = 3 +pad = 42 +pad_name = "P1.10" + +[[pins]] +pin = 4 +pad = 43 +pad_name = "P1.11" + +[[pins]] +pin = 5 +pad = 44 +pad_name = "P1.12" + +[[pins]] +pin = 6 +pad = 45 +pad_name = "P1.13" + +[[pins]] +pin = 7 +pad = 46 +pad_name = "P1.14" + +[[pins]] +pin = 8 +pad = 47 +pad_name = "P1.15" + +[[pins]] +pin = 9 +pad = 3 +pad_name = "P0.03" + +[[pins]] +pin = 10 +pad = 29 +pad_name = "P0.29" + +[[pins]] +pin = 11 +pad = 2 +pad_name = "P0.02" + +[[pins]] +pin = 12 +pad = 31 +pad_name = "P0.31" + +[[pins]] +pin = 13 +pad = 28 +pad_name = "P0.28" + +[[pins]] +pin = 14 +pad = 30 +pad_name = "P0.30" + +[[pins]] +pin = 15 +name = "GND" # non-GPIO + +[[pins]] +pin = 16 +pad = 27 +pad_name = "P0.27" + +[[pins]] +pin = 17 +pad = 0 +pad_name = "P0.00" + +[[pins]] +pin = 18 +pad = 1 +pad_name = "P0.01" + +[[pins]] +pin = 19 +pad = 26 +pad_name = "P0.26" + +[[pins]] +pin = 20 +pad = 4 +pad_name = "P0.04" + +[[pins]] +pin = 21 +pad = 5 +pad_name = "P0.05" + +[[pins]] +pin = 22 +pad = 6 +pad_name = "P0.06" + +[[pins]] +pin = 23 +pad = 7 +pad_name = "P0.07" + +[[pins]] +pin = 24 +pad = 8 +pad_name = "P0.08" + +[[pins]] +pin = 25 +pad = 40 +pad_name = "P1.08" + +[[pins]] +pin = 26 +pad = 41 +pad_name = "P1.09" + +[[pins]] +pin = 27 +pad = 11 +pad_name = "P0.11" + +[[pins]] +pin = 28 +name = "VDD" # non-GPIO + +[[pins]] +pin = 29 +pad = 12 +pad_name = "P0.12" + +[[pins]] +pin = 30 +name = "VDD" # non-GPIO + +[[pins]] +pin = 31 +name = "DCCH" # non-GPIO + +[[pins]] +pin = 32 +name = "VBUS" # non-GPIO + +[[pins]] +pin = 33 +name = "GND" # non-GPIO + +[[pins]] +pin = 34 +name = "D-" # non-GPIO + +[[pins]] +pin = 35 +name = "D+" # non-GPIO + +[[pins]] +pin = 36 +pad = 14 +pad_name = "P0.14" + +[[pins]] +pin = 37 +pad = 13 +pad_name = "P0.13" + +[[pins]] +pin = 38 +pad = 16 +pad_name = "P0.16" + +[[pins]] +pin = 39 +pad = 15 +pad_name = "P0.15" + +[[pins]] +pin = 40 +pad = 18 +pad_name = "P0.18" + +[[pins]] +pin = 41 +pad = 17 +pad_name = "P0.17" + +[[pins]] +pin = 42 +pad = 19 +pad_name = "P0.19" + +[[pins]] +pin = 43 +pad = 21 +pad_name = "P0.21" + +[[pins]] +pin = 44 +pad = 20 +pad_name = "P0.20" + +[[pins]] +pin = 45 +pad = 23 +pad_name = "P0.23" + +[[pins]] +pin = 46 +pad = 22 +pad_name = "P0.22" + +[[pins]] +pin = 47 +pad = 32 +pad_name = "P1.00" + +[[pins]] +pin = 48 +pad = 24 +pad_name = "P0.24" + +[[pins]] +pin = 49 +pad = 25 +pad_name = "P0.25" + +[[pins]] +pin = 50 +pad = 34 +pad_name = "P1.02" + +[[pins]] +pin = 51 +name = "SWDIO" # non-GPIO + +[[pins]] +pin = 52 +pad = 9 +pad_name = "P0.09" + +[[pins]] +pin = 53 +name = "SWDCLK" # non-GPIO + +[[pins]] +pin = 54 +pad = 10 +pad_name = "P0.10" + +[[pins]] +pin = 55 +name = "GND" # non-GPIO + +[[pins]] +pin = 56 +pad = 36 +pad_name = "P1.04" + +[[pins]] +pin = 57 +pad = 38 +pad_name = "P1.06" + +[[pins]] +pin = 58 +pad = 39 +pad_name = "P1.07" + +[[pins]] +pin = 59 +pad = 37 +pad_name = "P1.05" + +[[pins]] +pin = 60 +pad = 35 +pad_name = "P1.03" + +[[pins]] +pin = 61 +pad = 33 +pad_name = "P1.01" diff --git a/ports/zephyr-cp/modules/iobroker/packages/nrf52840_aqfn73.toml b/ports/zephyr-cp/modules/iobroker/packages/nrf52840_aqfn73.toml new file mode 100644 index 00000000000..00334ea58a1 --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/packages/nrf52840_aqfn73.toml @@ -0,0 +1,301 @@ +# Package pin map for nrf52840_aqfn73, transcribed from nRF52840_PS_v1.1.pdf, +# section 7.1.1 (aQFN73 ball assignments), +# by tools/gen_package.py. Review against the datasheet figure before +# editing by hand; regenerate instead when the datasheet changes. + +# Ball packages: 'pin' numbers the balls sequentially in row-major +# order (the datasheet only labels them, e.g. B2). + +name = "nrf52840_aqfn73" +socs = ["SOC_NRF52840_QIAA"] + +[[pins]] +pin = 1 +ball = "A8" +pad = 31 +pad_name = "P0.31" + +[[pins]] +pin = 2 +ball = "A10" +pad = 29 +pad_name = "P0.29" + +[[pins]] +pin = 3 +ball = "A12" +pad = 2 +pad_name = "P0.02" + +[[pins]] +pin = 4 +ball = "A14" +pad = 47 +pad_name = "P1.15" + +[[pins]] +pin = 5 +ball = "A16" +pad = 45 +pad_name = "P1.13" + +[[pins]] +pin = 7 +ball = "A20" +pad = 42 +pad_name = "P1.10" + +[[pins]] +pin = 14 +ball = "B9" +pad = 30 +pad_name = "P0.30" + +[[pins]] +pin = 15 +ball = "B11" +pad = 28 +pad_name = "P0.28" + +[[pins]] +pin = 16 +ball = "B13" +pad = 3 +pad_name = "P0.03" + +[[pins]] +pin = 17 +ball = "B15" +pad = 46 +pad_name = "P1.14" + +[[pins]] +pin = 18 +ball = "B17" +pad = 44 +pad_name = "P1.12" + +[[pins]] +pin = 19 +ball = "B19" +pad = 43 +pad_name = "P1.11" + +[[pins]] +pin = 22 +ball = "D2" +pad = 0 +pad_name = "P0.00" + +[[pins]] +pin = 25 +ball = "F2" +pad = 1 +pad_name = "P0.01" + +[[pins]] +pin = 27 +ball = "G1" +pad = 26 +pad_name = "P0.26" + +[[pins]] +pin = 28 +ball = "H2" +pad = 27 +pad_name = "P0.27" + +[[pins]] +pin = 30 +ball = "J1" +pad = 4 +pad_name = "P0.04" + +[[pins]] +pin = 31 +ball = "J24" +pad = 10 +pad_name = "P0.10" + +[[pins]] +pin = 32 +ball = "K2" +pad = 5 +pad_name = "P0.05" + +[[pins]] +pin = 33 +ball = "L1" +pad = 6 +pad_name = "P0.06" + +[[pins]] +pin = 34 +ball = "L24" +pad = 9 +pad_name = "P0.09" + +[[pins]] +pin = 35 +ball = "M2" +pad = 7 +pad_name = "P0.07" + +[[pins]] +pin = 36 +ball = "N1" +pad = 8 +pad_name = "P0.08" + +[[pins]] +pin = 38 +ball = "P2" +pad = 40 +pad_name = "P1.08" + +[[pins]] +pin = 39 +ball = "P23" +pad = 39 +pad_name = "P1.07" + +[[pins]] +pin = 40 +ball = "R1" +pad = 41 +pad_name = "P1.09" + +[[pins]] +pin = 41 +ball = "R24" +pad = 38 +pad_name = "P1.06" + +[[pins]] +pin = 42 +ball = "T2" +pad = 11 +pad_name = "P0.11" + +[[pins]] +pin = 43 +ball = "T23" +pad = 37 +pad_name = "P1.05" + +[[pins]] +pin = 44 +ball = "U1" +pad = 12 +pad_name = "P0.12" + +[[pins]] +pin = 45 +ball = "U24" +pad = 36 +pad_name = "P1.04" + +[[pins]] +pin = 46 +ball = "V23" +pad = 35 +pad_name = "P1.03" + +[[pins]] +pin = 48 +ball = "W24" +pad = 34 +pad_name = "P1.02" + +[[pins]] +pin = 50 +ball = "Y23" +pad = 33 +pad_name = "P1.01" + +[[pins]] +pin = 54 +ball = "AC9" +pad = 14 +pad_name = "P0.14" + +[[pins]] +pin = 55 +ball = "AC11" +pad = 16 +pad_name = "P0.16" + +[[pins]] +pin = 56 +ball = "AC13" +pad = 18 +pad_name = "P0.18" + +[[pins]] +pin = 57 +ball = "AC15" +pad = 19 +pad_name = "P0.19" + +[[pins]] +pin = 58 +ball = "AC17" +pad = 21 +pad_name = "P0.21" + +[[pins]] +pin = 59 +ball = "AC19" +pad = 23 +pad_name = "P0.23" + +[[pins]] +pin = 60 +ball = "AC21" +pad = 25 +pad_name = "P0.25" + +[[pins]] +pin = 65 +ball = "AD8" +pad = 13 +pad_name = "P0.13" + +[[pins]] +pin = 66 +ball = "AD10" +pad = 15 +pad_name = "P0.15" + +[[pins]] +pin = 67 +ball = "AD12" +pad = 17 +pad_name = "P0.17" + +[[pins]] +pin = 69 +ball = "AD16" +pad = 20 +pad_name = "P0.20" + +[[pins]] +pin = 70 +ball = "AD18" +pad = 22 +pad_name = "P0.22" + +[[pins]] +pin = 71 +ball = "AD20" +pad = 24 +pad_name = "P0.24" + +[[pins]] +pin = 72 +ball = "AD22" +pad = 32 +pad_name = "P1.00" + +# Package pins without a GPIO pad (not routable): +# A18, A22, A23, B1, B3, B5, B7, B24, C1, D23, E24, F23, H23, N24, W1, Y2, AA24, AB2, AC5, AC24, AD2, AD4, AD6, AD14, AD23 diff --git a/ports/zephyr-cp/modules/iobroker/packages/nrf5340_qkaa.toml b/ports/zephyr-cp/modules/iobroker/packages/nrf5340_qkaa.toml new file mode 100644 index 00000000000..3d278be5fb2 --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/packages/nrf5340_qkaa.toml @@ -0,0 +1,301 @@ +# Package pin map for nrf5340_qkaa, transcribed from nRF5340_PS_v1.6.pdf, +# section 9.1.1 (aQFN94 pin assignments), +# by tools/gen_package.py. Review against the datasheet figure before +# editing by hand; regenerate instead when the datasheet changes. + +# Ball packages: 'pin' numbers the balls sequentially in row-major +# order (the datasheet only labels them, e.g. B2). + +name = "nrf5340_qkaa" +socs = ["SOC_NRF5340_CPUAPP"] + +[[pins]] +pin = 4 +ball = "A17" +pad = 45 +pad_name = "P1.13" + +[[pins]] +pin = 16 +ball = "B14" +pad = 47 +pad_name = "P1.15" + +[[pins]] +pin = 17 +ball = "B16" +pad = 46 +pad_name = "P1.14" + +[[pins]] +pin = 18 +ball = "B18" +pad = 44 +pad_name = "P1.12" + +[[pins]] +pin = 19 +ball = "B20" +pad = 43 +pad_name = "P1.11" + +[[pins]] +pin = 20 +ball = "B22" +pad = 31 +pad_name = "P0.31" + +[[pins]] +pin = 21 +ball = "B24" +pad = 30 +pad_name = "P0.30" + +[[pins]] +pin = 39 +ball = "M2" +pad = 32 +pad_name = "P1.00" + +[[pins]] +pin = 40 +ball = "N1" +pad = 0 +pad_name = "P0.00" + +[[pins]] +pin = 42 +ball = "P2" +pad = 33 +pad_name = "P1.01" + +[[pins]] +pin = 43 +ball = "R1" +pad = 1 +pad_name = "P0.01" + +[[pins]] +pin = 44 +ball = "R31" +pad = 42 +pad_name = "P1.10" + +[[pins]] +pin = 47 +ball = "U31" +pad = 29 +pad_name = "P0.29" + +[[pins]] +pin = 48 +ball = "V2" +pad = 4 +pad_name = "P0.04" + +[[pins]] +pin = 49 +ball = "W1" +pad = 2 +pad_name = "P0.02" + +[[pins]] +pin = 51 +ball = "Y2" +pad = 5 +pad_name = "P0.05" + +[[pins]] +pin = 52 +ball = "AA1" +pad = 3 +pad_name = "P0.03" + +[[pins]] +pin = 54 +ball = "AB2" +pad = 6 +pad_name = "P0.06" + +[[pins]] +pin = 57 +ball = "AD2" +pad = 7 +pad_name = "P0.07" + +[[pins]] +pin = 58 +ball = "AE1" +pad = 34 +pad_name = "P1.02" + +[[pins]] +pin = 59 +ball = "AE31" +pad = 28 +pad_name = "P0.28" + +[[pins]] +pin = 60 +ball = "AF2" +pad = 35 +pad_name = "P1.03" + +[[pins]] +pin = 63 +ball = "AH2" +pad = 8 +pad_name = "P0.08" + +[[pins]] +pin = 64 +ball = "AJ1" +pad = 9 +pad_name = "P0.09" + +[[pins]] +pin = 66 +ball = "AK2" +pad = 10 +pad_name = "P0.10" + +[[pins]] +pin = 67 +ball = "AK4" +pad = 11 +pad_name = "P0.11" + +[[pins]] +pin = 68 +ball = "AK6" +pad = 12 +pad_name = "P0.12" + +[[pins]] +pin = 69 +ball = "AK8" +pad = 14 +pad_name = "P0.14" + +[[pins]] +pin = 70 +ball = "AK10" +pad = 15 +pad_name = "P0.15" + +[[pins]] +pin = 71 +ball = "AK12" +pad = 17 +pad_name = "P0.17" + +[[pins]] +pin = 72 +ball = "AK14" +pad = 18 +pad_name = "P0.18" + +[[pins]] +pin = 73 +ball = "AK16" +pad = 20 +pad_name = "P0.20" + +[[pins]] +pin = 74 +ball = "AK18" +pad = 22 +pad_name = "P0.22" + +[[pins]] +pin = 75 +ball = "AK20" +pad = 23 +pad_name = "P0.23" + +[[pins]] +pin = 76 +ball = "AK22" +pad = 37 +pad_name = "P1.05" + +[[pins]] +pin = 77 +ball = "AK24" +pad = 39 +pad_name = "P1.07" + +[[pins]] +pin = 78 +ball = "AK26" +pad = 41 +pad_name = "P1.09" + +[[pins]] +pin = 79 +ball = "AK28" +pad = 25 +pad_name = "P0.25" + +[[pins]] +pin = 80 +ball = "AK30" +pad = 27 +pad_name = "P0.27" + +[[pins]] +pin = 82 +ball = "AL5" +pad = 13 +pad_name = "P0.13" + +[[pins]] +pin = 84 +ball = "AL9" +pad = 16 +pad_name = "P0.16" + +[[pins]] +pin = 86 +ball = "AL13" +pad = 19 +pad_name = "P0.19" + +[[pins]] +pin = 87 +ball = "AL15" +pad = 21 +pad_name = "P0.21" + +[[pins]] +pin = 89 +ball = "AL19" +pad = 36 +pad_name = "P1.04" + +[[pins]] +pin = 90 +ball = "AL21" +pad = 38 +pad_name = "P1.06" + +[[pins]] +pin = 91 +ball = "AL23" +pad = 40 +pad_name = "P1.08" + +[[pins]] +pin = 93 +ball = "AL27" +pad = 24 +pad_name = "P0.24" + +[[pins]] +pin = 94 +ball = "AL29" +pad = 26 +pad_name = "P0.26" + +# Package pins without a GPIO pad (not routable): +# A5, A13, A15, A19, A21, A23, A25, A27, B2, B4, B6, B8, B10, B12, B26, B28, B30, C1, C31, D2, E1, E31, F2, G1, G31, H2, J1, J31, K2, L1, L31, N31, T2, U1, W31, AA31, AC1, AC31, AG1, AG31, AJ31, AL3, AL7, AL11, AL17, AL25 diff --git a/ports/zephyr-cp/modules/iobroker/packages/nrf54l15_qfn48.toml b/ports/zephyr-cp/modules/iobroker/packages/nrf54l15_qfn48.toml new file mode 100644 index 00000000000..63cddcf27b0 --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/packages/nrf54l15_qfn48.toml @@ -0,0 +1,165 @@ +# Package pin map for nrf54l15_qfn48, transcribed from nRF54L15_nRF54L10_nRF54L05_Datasheet_v1.0.pdf, +# section 10.1.4 (QFN48, QFAA package pin assignments), +# by tools/gen_package.py. Review against the datasheet figure before +# editing by hand; regenerate instead when the datasheet changes. + +name = "nrf54l15_qfn48" +socs = ["SOC_NRF54L15", "SOC_NRF54L10", "SOC_NRF54L05"] + +[[pins]] +pin = 1 +pad = 32 +pad_name = "P1.00" + +[[pins]] +pin = 2 +pad = 33 +pad_name = "P1.01" + +[[pins]] +pin = 3 +pad = 34 +pad_name = "P1.02" + +[[pins]] +pin = 4 +pad = 35 +pad_name = "P1.03" + +[[pins]] +pin = 5 +pad = 36 +pad_name = "P1.04" + +[[pins]] +pin = 6 +pad = 37 +pad_name = "P1.05" + +[[pins]] +pin = 7 +pad = 38 +pad_name = "P1.06" + +[[pins]] +pin = 8 +pad = 39 +pad_name = "P1.07" + +[[pins]] +pin = 9 +pad = 40 +pad_name = "P1.08" + +[[pins]] +pin = 11 +pad = 64 +pad_name = "P2.00" + +[[pins]] +pin = 12 +pad = 65 +pad_name = "P2.01" + +[[pins]] +pin = 13 +pad = 66 +pad_name = "P2.02" + +[[pins]] +pin = 14 +pad = 67 +pad_name = "P2.03" + +[[pins]] +pin = 15 +pad = 68 +pad_name = "P2.04" + +[[pins]] +pin = 16 +pad = 69 +pad_name = "P2.05" + +[[pins]] +pin = 17 +pad = 70 +pad_name = "P2.06" + +[[pins]] +pin = 18 +pad = 71 +pad_name = "P2.07" + +[[pins]] +pin = 19 +pad = 72 +pad_name = "P2.08" + +[[pins]] +pin = 20 +pad = 73 +pad_name = "P2.09" + +[[pins]] +pin = 21 +pad = 74 +pad_name = "P2.10" + +[[pins]] +pin = 23 +pad = 0 +pad_name = "P0.00" + +[[pins]] +pin = 24 +pad = 1 +pad_name = "P0.01" + +[[pins]] +pin = 27 +pad = 2 +pad_name = "P0.02" + +[[pins]] +pin = 28 +pad = 3 +pad_name = "P0.03" + +[[pins]] +pin = 29 +pad = 4 +pad_name = "P0.04" + +[[pins]] +pin = 37 +pad = 41 +pad_name = "P1.09" + +[[pins]] +pin = 38 +pad = 42 +pad_name = "P1.10" + +[[pins]] +pin = 39 +pad = 43 +pad_name = "P1.11" + +[[pins]] +pin = 40 +pad = 44 +pad_name = "P1.12" + +[[pins]] +pin = 41 +pad = 45 +pad_name = "P1.13" + +[[pins]] +pin = 42 +pad = 46 +pad_name = "P1.14" + +# Package pins without a GPIO pad (not routable): +# 10, 22, 25, 26, 30, 31, 32, 33, 34, 35, 36, 43, 44, 45, 46, 47, 48 diff --git a/ports/zephyr-cp/modules/iobroker/packages/nrf54lm20_csp98.toml b/ports/zephyr-cp/modules/iobroker/packages/nrf54lm20_csp98.toml new file mode 100644 index 00000000000..526d6e09619 --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/packages/nrf54lm20_csp98.toml @@ -0,0 +1,409 @@ +# Package pin map for nrf54lm20_csp98, transcribed from nRF54LM20A_nRF54LM20B_Datasheet_v1.0.pdf, +# section 10.1.4 (CSP98, PAAA package pin assignments), +# by tools/gen_package.py. Review against the datasheet figure before +# editing by hand; regenerate instead when the datasheet changes. + +# Ball packages: 'pin' numbers the balls sequentially in row-major +# order (the datasheet only labels them, e.g. B2). + +name = "nrf54lm20_csp98" +socs = ["SOC_NRF54LM20A", "SOC_NRF54LM20B"] + +[[pins]] +pin = 12 +ball = "B2" +pad = 47 +pad_name = "P1.15" + +[[pins]] +pin = 13 +ball = "B3" +pad = 48 +pad_name = "P1.16" + +[[pins]] +pin = 14 +ball = "B4" +pad = 49 +pad_name = "P1.17" + +[[pins]] +pin = 15 +ball = "B5" +pad = 50 +pad_name = "P1.18" + +[[pins]] +pin = 16 +ball = "B6" +pad = 51 +pad_name = "P1.19" + +[[pins]] +pin = 17 +ball = "B7" +pad = 55 +pad_name = "P1.23" + +[[pins]] +pin = 18 +ball = "B8" +pad = 58 +pad_name = "P1.26" + +[[pins]] +pin = 19 +ball = "B9" +pad = 70 +pad_name = "P2.06" + +[[pins]] +pin = 20 +ball = "B10" +pad = 64 +pad_name = "P2.00" + +[[pins]] +pin = 22 +ball = "C2" +pad = 46 +pad_name = "P1.14" + +[[pins]] +pin = 23 +ball = "C3" +pad = 45 +pad_name = "P1.13" + +[[pins]] +pin = 24 +ball = "C4" +pad = 44 +pad_name = "P1.12" + +[[pins]] +pin = 25 +ball = "C5" +pad = 43 +pad_name = "P1.11" + +[[pins]] +pin = 26 +ball = "C6" +pad = 52 +pad_name = "P1.20" + +[[pins]] +pin = 27 +ball = "C7" +pad = 53 +pad_name = "P1.21" + +[[pins]] +pin = 28 +ball = "C8" +pad = 59 +pad_name = "P1.27" + +[[pins]] +pin = 29 +ball = "C9" +pad = 71 +pad_name = "P2.07" + +[[pins]] +pin = 30 +ball = "C10" +pad = 65 +pad_name = "P2.01" + +[[pins]] +pin = 33 +ball = "D4" +pad = 108 +pad_name = "P3.12" + +[[pins]] +pin = 34 +ball = "D5" +pad = 42 +pad_name = "P1.10" + +[[pins]] +pin = 35 +ball = "D6" +pad = 54 +pad_name = "P1.22" + +[[pins]] +pin = 36 +ball = "D7" +pad = 56 +pad_name = "P1.24" + +[[pins]] +pin = 37 +ball = "D8" +pad = 60 +pad_name = "P1.28" + +[[pins]] +pin = 38 +ball = "D9" +pad = 72 +pad_name = "P2.08" + +[[pins]] +pin = 39 +ball = "D10" +pad = 66 +pad_name = "P2.02" + +[[pins]] +pin = 41 +ball = "E3" +pad = 9 +pad_name = "P0.09" + +[[pins]] +pin = 42 +ball = "E4" +pad = 107 +pad_name = "P3.11" + +[[pins]] +pin = 45 +ball = "E7" +pad = 57 +pad_name = "P1.25" + +[[pins]] +pin = 46 +ball = "E8" +pad = 41 +pad_name = "P1.09" + +[[pins]] +pin = 47 +ball = "E9" +pad = 73 +pad_name = "P2.09" + +[[pins]] +pin = 48 +ball = "E10" +pad = 67 +pad_name = "P2.03" + +[[pins]] +pin = 51 +ball = "F3" +pad = 8 +pad_name = "P0.08" + +[[pins]] +pin = 52 +ball = "F4" +pad = 106 +pad_name = "P3.10" + +[[pins]] +pin = 55 +ball = "F7" +pad = 105 +pad_name = "P3.09" + +[[pins]] +pin = 56 +ball = "F8" +pad = 40 +pad_name = "P1.08" + +[[pins]] +pin = 57 +ball = "F9" +pad = 74 +pad_name = "P2.10" + +[[pins]] +pin = 58 +ball = "F10" +pad = 68 +pad_name = "P2.04" + +[[pins]] +pin = 61 +ball = "G3" +pad = 7 +pad_name = "P0.07" + +[[pins]] +pin = 62 +ball = "G4" +pad = 104 +pad_name = "P3.08" + +[[pins]] +pin = 63 +ball = "G5" +pad = 103 +pad_name = "P3.07" + +[[pins]] +pin = 64 +ball = "G6" +pad = 102 +pad_name = "P3.06" + +[[pins]] +pin = 65 +ball = "G7" +pad = 101 +pad_name = "P3.05" + +[[pins]] +pin = 66 +ball = "G8" +pad = 39 +pad_name = "P1.07" + +[[pins]] +pin = 67 +ball = "G9" +pad = 69 +pad_name = "P2.05" + +[[pins]] +pin = 70 +ball = "H2" +pad = 6 +pad_name = "P0.06" + +[[pins]] +pin = 71 +ball = "H3" +pad = 5 +pad_name = "P0.05" + +[[pins]] +pin = 72 +ball = "H4" +pad = 99 +pad_name = "P3.03" + +[[pins]] +pin = 73 +ball = "H5" +pad = 98 +pad_name = "P3.02" + +[[pins]] +pin = 74 +ball = "H6" +pad = 97 +pad_name = "P3.01" + +[[pins]] +pin = 75 +ball = "H7" +pad = 96 +pad_name = "P3.00" + +[[pins]] +pin = 76 +ball = "H8" +pad = 37 +pad_name = "P1.05" + +[[pins]] +pin = 77 +ball = "H9" +pad = 35 +pad_name = "P1.03" + +[[pins]] +pin = 78 +ball = "H10" +pad = 61 +pad_name = "P1.29" + +[[pins]] +pin = 79 +ball = "J1" +pad = 4 +pad_name = "P0.04" + +[[pins]] +pin = 80 +ball = "J2" +pad = 3 +pad_name = "P0.03" + +[[pins]] +pin = 81 +ball = "J3" +pad = 2 +pad_name = "P0.02" + +[[pins]] +pin = 82 +ball = "J4" +pad = 100 +pad_name = "P3.04" + +[[pins]] +pin = 84 +ball = "J6" +pad = 33 +pad_name = "P1.01" + +[[pins]] +pin = 85 +ball = "J7" +pad = 34 +pad_name = "P1.02" + +[[pins]] +pin = 86 +ball = "J8" +pad = 38 +pad_name = "P1.06" + +[[pins]] +pin = 87 +ball = "J9" +pad = 36 +pad_name = "P1.04" + +[[pins]] +pin = 88 +ball = "J10" +pad = 62 +pad_name = "P1.30" + +[[pins]] +pin = 90 +ball = "K2" +pad = 1 +pad_name = "P0.01" + +[[pins]] +pin = 91 +ball = "K3" +pad = 0 +pad_name = "P0.00" + +[[pins]] +pin = 96 +ball = "K8" +pad = 32 +pad_name = "P1.00" + +[[pins]] +pin = 97 +ball = "K9" +pad = 63 +pad_name = "P1.31" + +# Package pins without a GPIO pad (not routable): +# A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, B1, C1, D1, D2, E1, E5, E6, F1, F2, F5, F6, G1, G2, G10, H1, J5, K1, K4, K5, K6, K7, K10 diff --git a/ports/zephyr-cp/modules/iobroker/packages/rp2040_qfn56.toml b/ports/zephyr-cp/modules/iobroker/packages/rp2040_qfn56.toml new file mode 100644 index 00000000000..d90b5ac8ea7 --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/packages/rp2040_qfn56.toml @@ -0,0 +1,159 @@ +# Package pin map for rp2040_qfn56, transcribed from rp2040-datasheet.pdf, +# Table 615 (GPIO pins), with a one-off pdftotext -layout parse. Review +# against the datasheet figure before editing by hand. +# +# RP2040 has a single GPIO port; pads are numbered 0-29 and pad_name uses +# the datasheet's GPIOx naming. + +name = "rp2040_qfn56" +socs = ["SOC_RP2040"] + +[[pins]] +pin = 2 +pad = 0 +pad_name = "GPIO0" + +[[pins]] +pin = 3 +pad = 1 +pad_name = "GPIO1" + +[[pins]] +pin = 4 +pad = 2 +pad_name = "GPIO2" + +[[pins]] +pin = 5 +pad = 3 +pad_name = "GPIO3" + +[[pins]] +pin = 6 +pad = 4 +pad_name = "GPIO4" + +[[pins]] +pin = 7 +pad = 5 +pad_name = "GPIO5" + +[[pins]] +pin = 8 +pad = 6 +pad_name = "GPIO6" + +[[pins]] +pin = 9 +pad = 7 +pad_name = "GPIO7" + +[[pins]] +pin = 11 +pad = 8 +pad_name = "GPIO8" + +[[pins]] +pin = 12 +pad = 9 +pad_name = "GPIO9" + +[[pins]] +pin = 13 +pad = 10 +pad_name = "GPIO10" + +[[pins]] +pin = 14 +pad = 11 +pad_name = "GPIO11" + +[[pins]] +pin = 15 +pad = 12 +pad_name = "GPIO12" + +[[pins]] +pin = 16 +pad = 13 +pad_name = "GPIO13" + +[[pins]] +pin = 17 +pad = 14 +pad_name = "GPIO14" + +[[pins]] +pin = 18 +pad = 15 +pad_name = "GPIO15" + +[[pins]] +pin = 27 +pad = 16 +pad_name = "GPIO16" + +[[pins]] +pin = 28 +pad = 17 +pad_name = "GPIO17" + +[[pins]] +pin = 29 +pad = 18 +pad_name = "GPIO18" + +[[pins]] +pin = 30 +pad = 19 +pad_name = "GPIO19" + +[[pins]] +pin = 31 +pad = 20 +pad_name = "GPIO20" + +[[pins]] +pin = 32 +pad = 21 +pad_name = "GPIO21" + +[[pins]] +pin = 34 +pad = 22 +pad_name = "GPIO22" + +[[pins]] +pin = 35 +pad = 23 +pad_name = "GPIO23" + +[[pins]] +pin = 36 +pad = 24 +pad_name = "GPIO24" + +[[pins]] +pin = 37 +pad = 25 +pad_name = "GPIO25" + +[[pins]] +pin = 38 +pad = 26 +pad_name = "GPIO26" + +[[pins]] +pin = 39 +pad = 27 +pad_name = "GPIO27" + +[[pins]] +pin = 40 +pad = 28 +pad_name = "GPIO28" + +[[pins]] +pin = 41 +pad = 29 +pad_name = "GPIO29" diff --git a/ports/zephyr-cp/modules/iobroker/src/iobroker.c b/ports/zephyr-cp/modules/iobroker/src/iobroker.c new file mode 100644 index 00000000000..965a27682f6 --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/src/iobroker.c @@ -0,0 +1,250 @@ +// iobroker: dynamic peripheral allocation and runtime pin routing. +// +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Adafruit Industries LLC +// +// SPDX-License-Identifier: MIT + +// SoC-agnostic core: package pin resolution, GPIO controller lookup and the +// claim registry. The bus allocate/release functions are SoC-specific and +// live in src///; the -ENOSYS stubs below stand in for SoCs +// with no implementation. + +#include +#include + +#include +#include + +#include "iobroker_internal.h" + +// One log instance for the whole module; iobroker_route.c declares it. +// Compiles away when CONFIG_LOG is off. +LOG_MODULE_REGISTER(iobroker, CONFIG_LOG_DEFAULT_LEVEL); + +int iobroker_gpio_split(uint16_t number, const struct device **port_out, + gpio_pin_t *pin_out) { + for (size_t i = 0; i < iobroker_gpio_port_count; i++) { + uint32_t base = (uint32_t)iobroker_gpio_port_indexes[i] * 32U; + if (number >= base && number < base + 32U) { + *port_out = iobroker_gpio_port_devices[i]; + *pin_out = (gpio_pin_t)(number - base); + return 0; + } + } + return -EINVAL; +} + +int iobroker_gpio_package_pin(uint8_t port, gpio_pin_t pin, + package_pin_t *package_pin_out) { + uint16_t soc_pad = (uint16_t)((uint32_t)port * 32U + pin); + for (size_t i = 0; i < iobroker_package_pin_count; i++) { + if (iobroker_package_pins[i].soc_pad == soc_pad) { + *package_pin_out = iobroker_package_pins[i].package_pin; + return 0; + } + } + return -EINVAL; +} + +int iobroker_package_pin_soc_pad(package_pin_t pin, uint16_t *soc_pad_out) { + if (pin == IOBROKER_NO_PIN) { + *soc_pad_out = IOBROKER_NO_PIN; + return 0; + } + for (size_t i = 0; i < iobroker_package_pin_count; i++) { + if (iobroker_package_pins[i].package_pin == pin) { + *soc_pad_out = iobroker_package_pins[i].soc_pad; + return 0; + } + } + return -EINVAL; +} + +#if !IOBROKER_ROUTING + +// SoCs without runtime routing: the bus allocate/release API still exists so +// busio can call it, but every allocate reports -ENOSYS. The implementations +// for routing SoCs live in src///. +int iobroker_i2c_allocate(package_pin_t sda, package_pin_t scl, + const struct device **dev_out) { + (void)sda; + (void)scl; + (void)dev_out; + return -ENOSYS; +} + +int iobroker_spi_allocate(package_pin_t clock, package_pin_t mosi, + package_pin_t miso, const struct device **dev_out) { + (void)clock; + (void)mosi; + (void)miso; + (void)dev_out; + return -ENOSYS; +} + +int iobroker_uart_allocate(package_pin_t tx, package_pin_t rx, + package_pin_t rts, package_pin_t cts, const struct device **dev_out) { + (void)tx; + (void)rx; + (void)rts; + (void)cts; + (void)dev_out; + return -ENOSYS; +} + +bool iobroker_release(const struct device *dev) { + (void)dev; + LOG_DBG("release: no routing support on this SoC, nothing to release"); + return false; +} + +#endif // !IOBROKER_ROUTING + +// Pins currently claimed for plain GPIO use. The module leaves the pad alone +// on allocate (the caller configures it) but returns it to a quiescent state +// on release. Claims store the GPIO controller device and pin number that the +// allocate call resolved and returned. +typedef struct { + const struct device *port; + gpio_pin_t number; + bool in_use; +} gpio_claim_t; + +static gpio_claim_t gpio_claims[CONFIG_IOBROKER_GPIO_MAX_PINS]; + +// Returns true when the package pin is claimed by a currently allocated +// instance or a GPIO allocation. Disconnected signals (IOBROKER_NO_PIN) +// and package pins with no entry in the map claim nothing. The bus loops +// below exist whenever the nRF pinctrl driver is built (the board emits the +// instance tables); without routing the tables are never marked in use. +bool iobroker_pin_in_use(package_pin_t pin) { + if (pin == IOBROKER_NO_PIN) { + return false; + } + #if defined(CONFIG_PINCTRL_NRF) + for (size_t i = 0; i < iobroker_i2c_bus_count; i++) { + if (!iobroker_i2c_bus_states[i].in_use) { + continue; + } + for (uint8_t j = 0; j < iobroker_i2c_bus_states[i].pin_count; j++) { + if (iobroker_i2c_bus_states[i].pins[j] == pin) { + return true; + } + } + } + for (size_t i = 0; i < iobroker_spi_bus_count; i++) { + if (!iobroker_spi_bus_states[i].in_use) { + continue; + } + for (uint8_t j = 0; j < iobroker_spi_bus_states[i].pin_count; j++) { + if (iobroker_spi_bus_states[i].pins[j] == pin) { + return true; + } + } + } + for (size_t i = 0; i < iobroker_uart_bus_count; i++) { + if (!iobroker_uart_bus_states[i].in_use) { + continue; + } + for (uint8_t j = 0; j < iobroker_uart_bus_states[i].pin_count; j++) { + if (iobroker_uart_bus_states[i].pins[j] == pin) { + return true; + } + } + } + #endif // CONFIG_PINCTRL_NRF + uint16_t soc_pad; + if (iobroker_package_pin_soc_pad(pin, &soc_pad) < 0) { + return false; + } + #if defined(CONFIG_PINCTRL_NRF) + // Pads owned by fixed peripherals (console UART, flash instance, ...) are + // always in use: their pinctrl state drives them from boot. + for (size_t i = 0; i < iobroker_reserved_pads_count; i++) { + if (iobroker_reserved_pads[i] == soc_pad) { + return true; + } + } + #endif + const struct device *port; + gpio_pin_t number; + if (iobroker_gpio_split(soc_pad, &port, &number) < 0) { + return false; + } + for (size_t i = 0; i < ARRAY_SIZE(gpio_claims); i++) { + if (gpio_claims[i].in_use && gpio_claims[i].port == port && + gpio_claims[i].number == number) { + return true; + } + } + return false; +} + +// Claim a package pin for GPIO use. The caller configures the pad itself +// while the claim is held; release returns it to a quiescent state. The pin +// is resolved through the package pin map, and the GPIO controller device and +// pin number within it are returned. +int iobroker_gpio_allocate(package_pin_t pin, + const struct device **port_out, gpio_pin_t *pin_out) { + if (pin == IOBROKER_NO_PIN) { + LOG_WRN("gpio allocate: pin is disconnected"); + return -EINVAL; + } + if (iobroker_pin_in_use(pin)) { + LOG_WRN("gpio allocate: package pin %u already claimed", (unsigned)pin); + return -EBUSY; + } + uint16_t soc_pad; + int ret = iobroker_package_pin_soc_pad(pin, &soc_pad); + if (ret < 0) { + LOG_WRN("gpio allocate: package pin %u not in package map", (unsigned)pin); + return ret; + } + ret = iobroker_gpio_split(soc_pad, port_out, pin_out); + if (ret < 0) { + LOG_WRN("gpio allocate: no GPIO controller for pad %u", (unsigned)soc_pad); + return ret; + } + for (size_t i = 0; i < ARRAY_SIZE(gpio_claims); i++) { + if (gpio_claims[i].in_use) { + continue; + } + gpio_claims[i].port = *port_out; + gpio_claims[i].number = *pin_out; + gpio_claims[i].in_use = true; + LOG_DBG("gpio allocate: package pin %u -> %s pin %u", + (unsigned)pin, (*port_out)->name, (unsigned)*pin_out); + return 0; + } + LOG_WRN("gpio allocate: claim registry full (%u pins)", + (unsigned)CONFIG_IOBROKER_GPIO_MAX_PINS); + return -ENOMEM; +} + +// Return a pad to a quiescent state: disconnected from any peripheral +// routing, input buffer and driver off, no pulls. +static void gpio_deconfigure(const struct device *port, gpio_pin_t number) { + if (gpio_pin_configure(port, number, GPIO_DISCONNECTED) == -ENOTSUP) { + // SoCs without GPIO_DISCONNECTED support settle for a plain input. + gpio_pin_configure(port, number, GPIO_INPUT); + } +} + +// Release a GPIO claim and return the pad to a quiescent state. Pass the +// device and pin number that the allocate call returned. Returns true when a +// claim was held. +bool iobroker_gpio_release(const struct device *port, gpio_pin_t number) { + for (size_t i = 0; i < ARRAY_SIZE(gpio_claims); i++) { + if (gpio_claims[i].in_use && gpio_claims[i].port == port && + gpio_claims[i].number == number) { + gpio_claims[i].in_use = false; + gpio_deconfigure(port, number); + LOG_DBG("gpio release: %s pin %u back to quiescent state", + port->name, (unsigned)number); + return true; + } + } + return false; +} diff --git a/ports/zephyr-cp/modules/iobroker/src/iobroker_internal.h b/ports/zephyr-cp/modules/iobroker/src/iobroker_internal.h new file mode 100644 index 00000000000..fa8e1afe6a3 --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/src/iobroker_internal.h @@ -0,0 +1,19 @@ +// iobroker internal helpers shared between the SoC-agnostic core and the +// vendor/SoC implementations in src///. Not part of the public +// API; see include/iobroker/iobroker.h for that. +// +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Adafruit Industries LLC +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +// Resolve a package pin to the SoC pad it is bonded to (defined by the core). +// IOBROKER_NO_PIN passes through unchanged so that disconnected optional +// signals stay disconnected. Returns 0, or -EINVAL when the pin is not in +// the map. +int iobroker_package_pin_soc_pad(package_pin_t pin, uint16_t *soc_pad_out); diff --git a/ports/zephyr-cp/modules/iobroker/src/nordic/nrf/iobroker_route.c b/ports/zephyr-cp/modules/iobroker/src/nordic/nrf/iobroker_route.c new file mode 100644 index 00000000000..330b9aae1ea --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/src/nordic/nrf/iobroker_route.c @@ -0,0 +1,459 @@ +// iobroker: nRF runtime pin routing. +// +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Adafruit Industries LLC +// +// SPDX-License-Identifier: MIT + +// / = nordic/nrf: one implementation covers every nRF SoC, +// because their pin control encoding is a simple bit-packed value (see +// zephyr/dt-bindings/pinctrl/nrf-pinctrl.h and soc/nordic/common/pinctrl_soc.h) +// that can be computed at runtime, and any peripheral function can be routed +// to (almost) any pin via PSEL. +// +// Compiled whenever CONFIG_PINCTRL_NRF is on. The allocate/release +// definitions here only exist with the full feature set +// (CONFIG_PINCTRL_DYNAMIC and CONFIG_DEVICE_DEINIT_SUPPORT); otherwise the +// stubs in the core report -ENOSYS. + +#include +#include +#include + +#include +#include +#include +#include + +#include "iobroker_internal.h" + +LOG_MODULE_DECLARE(iobroker, CONFIG_LOG_DEFAULT_LEVEL); + +#if IOBROKER_ROUTING + +// Bit positions/fields replicated from nrf-pinctrl.h so that entries can be +// encoded at runtime instead of by the DT macros. +#define NRF_PSEL_FUN(fun) (((uint32_t)(fun) & NRF_FUN_MSK) << NRF_FUN_POS) +#define NRF_PSEL_PIN(pin) (((uint32_t)(pin) & NRF_PIN_MSK) << NRF_PIN_POS) +#define NRF_PSEL_DISCONNECT(fun) \ + ((uint32_t)NRF_PIN_DISCONNECTED | NRF_PSEL_FUN(fun)) +#define NRF_PSEL_PULL_UP ((uint32_t)NRF_PULL_UP << NRF_PULL_POS) +#define NRF_PSEL_LP ((uint32_t)NRF_LP_ENABLE << NRF_LP_POS) +#define NRF_PSEL_CLOCKPIN BIT(NRF_CLOCKPIN_ENABLE_POS) +// Pin + function bits, used to compare a request against a devicetree state. +#define NRF_PSEL_PINFUN_MASK \ + (((uint32_t)NRF_PIN_MSK << NRF_PIN_POS) | ((uint32_t)NRF_FUN_MSK << NRF_FUN_POS)) + +// Log decode helpers: turn a fun code and a SoC pad into readable names, so +// that the logs say which peripheral function goes to which GPIO. +static const char *nrf_fun_name(uint32_t fun) { + static const char *const names[] = { + "UART_TX", "UART_RX", "UART_RTS", "UART_CTS", + "SPIM_SCK", "SPIM_MOSI", "SPIM_MISO", + "SPIS_SCK", "SPIS_MOSI", "SPIS_MISO", "SPIS_CSN", + "TWIM_SCL", "TWIM_SDA", + }; + if (fun < ARRAY_SIZE(names)) { + return names[fun]; + } + return "(other)"; +} + +// Render a SoC pad as a GPIO name ("P0.09"), or "disconnected" for +// IOBROKER_NO_PIN. Writes into buf; returns buf for chaining. +static const char *nrf_pad_name(uint32_t soc_pad, char *buf, size_t size) { + if (soc_pad == IOBROKER_NO_PIN) { + return "disconnected"; + } + snprintf(buf, size, "P%u.%02u", (unsigned)(soc_pad / 32U), + (unsigned)(soc_pad % 32U)); + return buf; +} + +// Decode one pinctrl entry into flags text, e.g. "pull-up clockpin". buf +// receives an empty string when neither flag is set. +static const char *nrf_pin_flags(uint32_t psel, char *buf, size_t size) { + buf[0] = '\0'; + size_t used = 0; + uint32_t pull = NRF_GET_PULL(psel); + if (pull == NRF_PULL_UP) { + used += (size_t)snprintf(buf + used, size - used, "pull-up"); + } else if (pull == NRF_PULL_DOWN) { + used += (size_t)snprintf(buf + used, size - used, "pull-down"); + } + if (NRF_GET_CLOCKPIN_ENABLE(psel)) { + if (used > 0) { + used += (size_t)snprintf(buf + used, size - used, " "); + } + snprintf(buf + used, size - used, "clockpin"); + } + return buf; +} + +// Check that a SoC pad can be encoded (it sits on a known GPIO controller, +// so its number is a valid nRF pin). +static bool nrf_pad_ok(uint16_t soc_pad) { + if (soc_pad == IOBROKER_NO_PIN) { + return true; + } + const struct device *port; + gpio_pin_t number; + return iobroker_gpio_split(soc_pad, &port, &number) == 0; +} + +// Encode one nRF pin control entry. soc_pad may be IOBROKER_NO_PIN to +// leave the signal disconnected. pull_up enables the internal pull resistor; +// the caller picks it per bus signal (I2C SDA/SCL and UART RX idle high). +static pinctrl_soc_pin_t nrf_psel_encode(uint32_t fun, uint16_t soc_pad, + bool pull_up) { + uint32_t psel; + + if (soc_pad != IOBROKER_NO_PIN) { + psel = NRF_PSEL_PIN(soc_pad) | NRF_PSEL_FUN(fun); + // On nRF54 the GPIO pin clock must be enabled for signals that drive + // the pad. The pinctrl driver ignores this bit where unsupported + // (nRF52/nRF53). + switch (fun) { + case NRF_FUN_TWIM_SDA: + case NRF_FUN_TWIM_SCL: + case NRF_FUN_SPIM_SCK: + case NRF_FUN_SPIM_MOSI: + case NRF_FUN_UART_TX: + psel |= NRF_PSEL_CLOCKPIN; + break; + default: + break; + } + } else { + psel = NRF_PSEL_DISCONNECT(fun); + } + + if (pull_up) { + psel |= NRF_PSEL_PULL_UP; + } + + return psel; +} + +// Check whether a set of requested entries matches the devicetree default +// state of an instance (pin + function only; configuration bits ignored). +static bool nrf_psels_match(const pinctrl_soc_pin_t *psels, uint8_t count, + const pinctrl_soc_pin_t *requested, uint8_t requested_count) { + uint32_t mask = NRF_PSEL_PINFUN_MASK; + + for (uint8_t i = 0; i < requested_count; i++) { + bool found = false; + for (uint8_t j = 0; j < count; j++) { + if ((psels[j] & mask) == (requested[i] & mask)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + // The DT state must not use extra pins either. + for (uint8_t j = 0; j < count; j++) { + bool found = false; + for (uint8_t i = 0; i < requested_count; i++) { + if ((psels[j] & mask) == (requested[i] & mask)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; +} + +// Re-point an instance at the requested pins: ensure the device is +// de-initialized (initialization is the caller's job), then swap its pinctrl +// states for entries built at runtime. The caller initializes the device +// afterwards, which applies the new "default" state. +static int iobroker_route(const iobroker_instance_t *inst, iobroker_state_t *state, + const pinctrl_soc_pin_t *pins, uint8_t pin_count) { + if (pin_count > IOBROKER_MAX_PINS) { + return -EINVAL; + } + + LOG_INF("routing %s: de-initializing then swapping in %u runtime psels", + inst->dev->name, (unsigned)pin_count); + int ret = device_deinit(inst->dev); + if (ret < 0 && ret != -ENOSYS && ret != -EPERM) { + LOG_WRN("routing %s: device_deinit failed: %d", inst->dev->name, ret); + return ret; + } + + // The Zephyr pinctrl core requires the same set of state ids to be + // provided, so mirror the ids the device currently has. + uint8_t state_cnt = MIN(inst->pcfg->state_cnt, ARRAY_SIZE(state->states)); + if (state_cnt == 0) { + return -ENOENT; + } + + for (uint8_t i = 0; i < pin_count; i++) { + state->default_pins[i] = pins[i]; + // Sleep state: same pins, low power (input buffer disconnected). + state->sleep_pins[i] = pins[i] | NRF_PSEL_LP; + } + for (uint8_t s = 0; s < state_cnt; s++) { + uint8_t id = inst->pcfg->states[s].id; + state->states[s].id = id; + if (id == PINCTRL_STATE_SLEEP) { + state->states[s].pins = state->sleep_pins; + } else { + state->states[s].pins = state->default_pins; + } + state->states[s].pin_cnt = pin_count; + } + + ret = pinctrl_update_states(inst->pcfg, state->states, state_cnt); + if (ret < 0) { + LOG_WRN("routing %s: pinctrl_update_states failed: %d", + inst->dev->name, ret); + } else { + for (uint8_t i = 0; i < pin_count; i++) { + uint32_t psel = pins[i]; + char pad[12]; + char flags[24]; + LOG_INF("routing %s: psel[%u] = 0x%08x -> %s %s %s", + inst->dev->name, (unsigned)i, (unsigned)psel, + nrf_fun_name(NRF_GET_FUN(psel)), + nrf_pad_name(NRF_GET_PIN(psel), pad, sizeof(pad)), + nrf_pin_flags(psel, flags, sizeof(flags))); + } + } + return ret; +} + +// Allocate an instance from a pool. Instances that the board enabled with +// disconnected pins are routed dynamically to the requested pins. Instances +// with fixed devicetree pins are only used when the request matches their +// existing state exactly. The requested pins are recorded in the instance's +// state so that iobroker_pin_in_use() can report them. +static int iobroker_allocate(const char *kind, const iobroker_instance_t *buses, + size_t count, iobroker_state_t *states, const package_pin_t *requested, + const pinctrl_soc_pin_t *pins, uint8_t pin_count, const struct device **dev_out) { + for (size_t i = 0; i < count; i++) { + iobroker_state_t *state = &states[i]; + if (state->in_use) { + continue; + } + + if (buses[i].dt_psels == NULL) { + int ret = iobroker_route(&buses[i], state, pins, pin_count); + if (ret < 0) { + continue; + } + state->in_use = true; + state->routed = true; + state->pin_count = pin_count; + for (uint8_t j = 0; j < pin_count; j++) { + state->pins[j] = requested[j]; + } + *dev_out = buses[i].dev; + LOG_INF("%s: allocated %s, routed to %u package pins", + kind, buses[i].dev->name, (unsigned)pin_count); + return 0; + } + + if (nrf_psels_match(buses[i].dt_psels, buses[i].dt_psel_count, pins, pin_count)) { + // Already routed to these pins by the devicetree; just claim it. + state->in_use = true; + state->routed = false; + state->pin_count = pin_count; + for (uint8_t j = 0; j < pin_count; j++) { + state->pins[j] = requested[j]; + } + *dev_out = buses[i].dev; + LOG_INF("%s: allocated %s, devicetree pins match request", + kind, buses[i].dev->name); + return 0; + } + } + LOG_WRN("%s: no free instance for the requested pins", kind); + return -ENODEV; +} + +// Returns -EBUSY when any requested pin is already claimed by an allocated +// instance, so that a pin is only ever used by one allocate() call at a time. +static int iobroker_check_request(const char *kind, const package_pin_t *pins, + size_t count) { + for (size_t i = 0; i < count; i++) { + if (iobroker_pin_in_use(pins[i])) { + LOG_WRN("%s: package pin %u already in use", kind, + (unsigned)pins[i]); + return -EBUSY; + } + } + return 0; +} + +static bool iobroker_state_find(const struct device *dev, iobroker_state_t **state_out) { + for (size_t i = 0; i < iobroker_i2c_bus_count; i++) { + if (iobroker_i2c_buses[i].dev == dev) { + *state_out = &iobroker_i2c_bus_states[i]; + return true; + } + } + for (size_t i = 0; i < iobroker_spi_bus_count; i++) { + if (iobroker_spi_buses[i].dev == dev) { + *state_out = &iobroker_spi_bus_states[i]; + return true; + } + } + for (size_t i = 0; i < iobroker_uart_bus_count; i++) { + if (iobroker_uart_buses[i].dev == dev) { + *state_out = &iobroker_uart_bus_states[i]; + return true; + } + } + return false; +} + +bool iobroker_release(const struct device *dev) { + iobroker_state_t *state = NULL; + if (!iobroker_state_find(dev, &state)) { + LOG_WRN("release: %s is not an iobroker-managed instance", + dev == NULL ? "(null)" : dev->name); + return false; + } + bool routed = state->routed; + // De-init so that the device ends up de-initialized (like deferred-init + // devices are after boot); the caller initializes it again when it + // allocates the instance next. + (void)device_deinit(dev); + state->in_use = false; + state->routed = false; + state->pin_count = 0; + LOG_INF("released %s (was dynamically routed: %u)", dev->name, + (unsigned)routed); + return routed; +} + +int iobroker_i2c_allocate(package_pin_t sda, package_pin_t scl, + const struct device **dev_out) { + LOG_INF("i2c allocate: sda=%u scl=%u", (unsigned)sda, (unsigned)scl); + const package_pin_t requested[] = { sda, scl }; + int ret = iobroker_check_request("i2c", requested, 2); + if (ret < 0) { + return ret; + } + uint16_t sda_pad; + uint16_t scl_pad; + if (iobroker_package_pin_soc_pad(sda, &sda_pad) < 0 || + iobroker_package_pin_soc_pad(scl, &scl_pad) < 0) { + LOG_WRN("i2c allocate: package pin %u or %u not in package map", + (unsigned)sda, (unsigned)scl); + return -EINVAL; + } + if (!nrf_pad_ok(sda_pad) || !nrf_pad_ok(scl_pad)) { + LOG_WRN("i2c allocate: pad %u or %u is not on a GPIO controller", + (unsigned)sda_pad, (unsigned)scl_pad); + return -EINVAL; + } + char sda_name[12]; + char scl_name[12]; + LOG_INF("i2c allocate: SDA package pin %u -> %s, SCL package pin %u -> %s", + (unsigned)sda, nrf_pad_name(sda_pad, sda_name, sizeof(sda_name)), + (unsigned)scl, nrf_pad_name(scl_pad, scl_name, sizeof(scl_name))); + pinctrl_soc_pin_t pins[2]; + // Open-drain bus: both lines idle high via the internal pull-up. + pins[0] = nrf_psel_encode(NRF_FUN_TWIM_SDA, sda_pad, true); + pins[1] = nrf_psel_encode(NRF_FUN_TWIM_SCL, scl_pad, true); + return iobroker_allocate("i2c", iobroker_i2c_buses, iobroker_i2c_bus_count, + iobroker_i2c_bus_states, requested, pins, 2, dev_out); +} + +int iobroker_spi_allocate(package_pin_t clock, package_pin_t mosi, + package_pin_t miso, const struct device **dev_out) { + LOG_INF("spi allocate: clock=%u mosi=%u miso=%u", (unsigned)clock, + (unsigned)mosi, (unsigned)miso); + const package_pin_t requested[] = { clock, mosi, miso }; + int ret = iobroker_check_request("spi", requested, 3); + if (ret < 0) { + return ret; + } + uint16_t clock_pad; + uint16_t mosi_pad; + uint16_t miso_pad; + if (iobroker_package_pin_soc_pad(clock, &clock_pad) < 0 || + iobroker_package_pin_soc_pad(mosi, &mosi_pad) < 0 || + iobroker_package_pin_soc_pad(miso, &miso_pad) < 0) { + LOG_WRN("spi allocate: a package pin (%u/%u/%u) is not in the map", + (unsigned)clock, (unsigned)mosi, (unsigned)miso); + return -EINVAL; + } + if (!nrf_pad_ok(clock_pad) || !nrf_pad_ok(mosi_pad) || !nrf_pad_ok(miso_pad)) { + LOG_WRN("spi allocate: pad %u/%u/%u is not on a GPIO controller", + (unsigned)clock_pad, (unsigned)mosi_pad, (unsigned)miso_pad); + return -EINVAL; + } + char clock_name[12]; + char mosi_name[12]; + char miso_name[12]; + LOG_INF("spi allocate: SCK package pin %u -> %s, MOSI %u -> %s, MISO %u -> %s", + (unsigned)clock, nrf_pad_name(clock_pad, clock_name, sizeof(clock_name)), + (unsigned)mosi, nrf_pad_name(mosi_pad, mosi_name, sizeof(mosi_name)), + (unsigned)miso, nrf_pad_name(miso_pad, miso_name, sizeof(miso_name))); + pinctrl_soc_pin_t pins[3]; + // All signals are push-pull outputs (MISO from the peripheral's view). + pins[0] = nrf_psel_encode(NRF_FUN_SPIM_SCK, clock_pad, false); + pins[1] = nrf_psel_encode(NRF_FUN_SPIM_MOSI, mosi_pad, false); + pins[2] = nrf_psel_encode(NRF_FUN_SPIM_MISO, miso_pad, false); + return iobroker_allocate("spi", iobroker_spi_buses, iobroker_spi_bus_count, + iobroker_spi_bus_states, requested, pins, 3, dev_out); +} + +int iobroker_uart_allocate(package_pin_t tx, package_pin_t rx, + package_pin_t rts, package_pin_t cts, const struct device **dev_out) { + LOG_INF("uart allocate: tx=%u rx=%u rts=%u cts=%u", (unsigned)tx, + (unsigned)rx, (unsigned)rts, (unsigned)cts); + const package_pin_t requested[] = { tx, rx, rts, cts }; + int ret = iobroker_check_request("uart", requested, 4); + if (ret < 0) { + return ret; + } + uint16_t tx_pad; + uint16_t rx_pad; + uint16_t rts_pad; + uint16_t cts_pad; + if (iobroker_package_pin_soc_pad(tx, &tx_pad) < 0 || + iobroker_package_pin_soc_pad(rx, &rx_pad) < 0 || + iobroker_package_pin_soc_pad(rts, &rts_pad) < 0 || + iobroker_package_pin_soc_pad(cts, &cts_pad) < 0) { + LOG_WRN("uart allocate: a package pin (%u/%u/%u/%u) is not in the map", + (unsigned)tx, (unsigned)rx, (unsigned)rts, (unsigned)cts); + return -EINVAL; + } + if (!nrf_pad_ok(tx_pad) || !nrf_pad_ok(rx_pad) || + !nrf_pad_ok(rts_pad) || !nrf_pad_ok(cts_pad)) { + LOG_WRN("uart allocate: pad %u/%u/%u/%u is not on a GPIO controller", + (unsigned)tx_pad, (unsigned)rx_pad, (unsigned)rts_pad, + (unsigned)cts_pad); + return -EINVAL; + } + char tx_name[12]; + char rx_name[12]; + char rts_name[12]; + char cts_name[12]; + LOG_INF("uart allocate: TX package pin %u -> %s, RX %u -> %s, RTS %u -> %s, CTS %u -> %s", + (unsigned)tx, nrf_pad_name(tx_pad, tx_name, sizeof(tx_name)), + (unsigned)rx, nrf_pad_name(rx_pad, rx_name, sizeof(rx_name)), + (unsigned)rts, nrf_pad_name(rts_pad, rts_name, sizeof(rts_name)), + (unsigned)cts, nrf_pad_name(cts_pad, cts_name, sizeof(cts_name))); + pinctrl_soc_pin_t pins[4]; + // RX floats until the peer drives it, so pull it up internally. + pins[0] = nrf_psel_encode(NRF_FUN_UART_TX, tx_pad, false); + pins[1] = nrf_psel_encode(NRF_FUN_UART_RX, rx_pad, true); + pins[2] = nrf_psel_encode(NRF_FUN_UART_RTS, rts_pad, false); + pins[3] = nrf_psel_encode(NRF_FUN_UART_CTS, cts_pad, false); + return iobroker_allocate("uart", iobroker_uart_buses, iobroker_uart_bus_count, + iobroker_uart_bus_states, requested, pins, 4, dev_out); +} + +#endif // IOBROKER_ROUTING diff --git a/ports/zephyr-cp/modules/iobroker/tools/gen_package.py b/ports/zephyr-cp/modules/iobroker/tools/gen_package.py new file mode 100644 index 00000000000..0240cc3601c --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/tools/gen_package.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python3 +"""Transcribe a package pin assignment table into a iobroker package TOML. + +Input is a `pdftotext -layout` dump of a Nordic SoC datasheet (the module's +datasheets/ directory keeps the PDFs; run pdftotext -layout yourself). The +script locates one "X.Y.Z () package pin assignments" section, +parses its pin table and cross-checks the GPIO pads it finds against the pads +named in the section's pin assignment figure. + +The pin tables are laid out with the pin number vertically centered in its +row, so a pin's GPIO pad name can appear on the line before its number. The +parser therefore assigns every GPIO pad (P.) to the next pin +number at or after it in reading order; sequential pin numbering makes that +unambiguous. Every mapped pin is printed for manual review -- the datasheet +figure remains the source of truth, so diff the output against it. + +Usage (nRF54 datasheets, " () package pin assignments" with +numeric QFN pin numbers or a ball grid): + + gen_package.py DATASHEET.txt --section 10.1.4 --name nrf54l15_qfn48 \ + --socs SOC_NRF54L15,SOC_NRF54L10,SOC_NRF54L05 --pins 48 \ + --source nRF54L15_nRF54L10_nRF54L05_Datasheet_v1.0.pdf \ + --out ../packages/nrf54l15_qfn48.toml + +Ball grids label their pins "A5"-style instead of numbering them; pass the +grid's row letters with --rows (comma-separated when the grid has two-letter +rows, as the aQFN grids do: --rows A,B,...,Y,AA,...,AL) and --cols. + +nRF52/nRF53 product specifications name their sections " pin/ball +assignments" without a package code and lay the table out the same way (the +"Pin" column holds the ball label, there is no Clock column, and the aQFN +tables follow the main table with a "Corner pads" subsection); the same +options transcribe them: + + gen_package.py DATASHEET.txt --section 9.1.1 --name nrf5340_qkaa \ + --socs SOC_NRF5340_CPUAPP --pins 94 \ + --rows A,B,C,D,E,F,G,H,J,K,L,M,N,P,R,T,U,V,W,Y,AA,AB,AC,AD,AE,AF,AG,AH,AJ,AK,AL \ + --cols 31 --source nRF5340_PS_v1.6.pdf --out ../packages/nrf5340_qkaa.toml +""" + +import argparse +import re +import sys +from pathlib import Path + +PAD_RE = re.compile(r"P(\d+)\.(\d+)") +# Section headings: "10.1.4 QFN48 (QFAA) package pin assignments" (nRF54 +# datasheets; title plus package code) or "9.1.1 aQFN94 pin assignments" / +# "7.1.1 aQFN73 ball assignments" (nRF52840/nRF5340 product specifications; no +# package code). The heading must end in the assignments phrase, which keeps +# table-of-contents lines (trailing dot leaders and page numbers) out. +SECTION_RE = re.compile( + r"^\s*(\d+\.\d+\.\d+)\s+(.+?)\s+((?:package\s+)?(?:pin|ball)\s+assignments)$" +) +# Optional "()" at the end of a section title, as in the nRF54 headings. +TITLE_CODE_RE = re.compile(r"^(.*?)\s*\((\S+)\)$") +FIGURE_END_RE = re.compile(r"^\s*Figure \d+:") +# A pin number line: optionally preceded by the "Yes" clock-pin marker, either +# alone or with the row's first name/function entry on the same line. +PIN_LINE_RE = re.compile(r"^\s*(?:Yes\s+)?(\d+)(?:\s+(\S.*))?$") +# A ball name line (aQFN/CSP/BGA packages): row letter(s) + column at column 0. +BALL_LINE_RE = re.compile(r"^([A-Z]+)(\d+)(?:\s+|$)") +TABLE_HEADER_RE = re.compile(r"^\s*Pin\s+(?:Clock\s+)?Name\s+Function\s+Description") +# The pin table ends at its "Table N:" caption, or -- for aQFN packages, +# which list their corner pads right after the main table -- at the +# "Corner pads" heading. Neither belongs to the table. +TABLE_END_RE = re.compile(r"^\s*(?:Table\s+\d+:|Corner pads\b)") + + +def find_section(lines, section): + """Return (title, code, kind, section lines) for a numbered heading. + + code is the package code in the nRF54 "QFN48 (QFAA)" headings, or None + when the heading carries none ("aQFN94 pin assignments"). kind is the + heading's "package pin assignments"/"pin assignments"/"ball assignments" + phrase, used in the output header comment. + """ + starts = [(i, SECTION_RE.match(line)) for i, line in enumerate(lines)] + starts = [(i, m) for i, m in starts if m] + for index, (i, m) in enumerate(starts): + if m.group(1) == section: + end = starts[index + 1][0] if index + 1 < len(starts) else len(lines) + title, code = m.group(2), None + code_match = TITLE_CODE_RE.match(title) + if code_match: + title, code = code_match.group(1), code_match.group(2) + return title, code, m.group(3), lines[i + 1 : end] + raise SystemExit(f"section {section!r} not found") + + +def parse_table(section_lines, rows=None, cols=10): + """Return ({pin_id: [(port, pin), ...]}, ordered pin ids) from the table. + + Numeric packages (QFN) use 1-based pin numbers validated against + sequential numbering. Ball packages pass the grid's row letters (a list, + since aQFN grids have two-letter rows); pins are (row, column) pairs, + ordered row-major. + """ + header = next((i for i, line in enumerate(section_lines) if TABLE_HEADER_RE.match(line)), None) + if header is None: + raise SystemExit("pin table header ('Pin Clock Name Function ...') not found") + body = section_lines[header + 1 :] + # The table ends at its caption line or at the "Corner pads" subsection + # some packages list after it; neither is part of the table. + end = next((i for i, line in enumerate(body) if TABLE_END_RE.match(line)), None) + if end is not None: + body = body[:end] + + # Pass 1: pin lines. Numeric mode validates against sequential numbering + # so page numbers and stray digits cannot be mistaken for pins; ball mode + # validates against the grid instead. + number_lines = [] + if rows: + valid = {(row, column) for row in rows for column in range(1, cols + 1)} + for i, line in enumerate(body): + m = BALL_LINE_RE.match(line) + if m and (m.group(1), int(m.group(2))) in valid: + ball = (m.group(1), int(m.group(2))) + if ball in (b for _, b in number_lines): + raise SystemExit(f"duplicate ball {ball[0]}{ball[1]}") + number_lines.append((i, ball)) + else: + for i, line in enumerate(body): + m = PIN_LINE_RE.match(line) + if m and int(m.group(1)) == len(number_lines) + 1: + number_lines.append((i, int(m.group(1)))) + if not number_lines: + raise SystemExit("no pin numbers found; table layout mismatch?") + + # Pass 2: assign each GPIO pad to the next pin at or after it. The pin + # number is vertically centered in its table row, so the row's GPIO pad + # name can sit on the line before the number. + pins = {pin: [] for _, pin in number_lines} + for i, line in enumerate(body): + for m in PAD_RE.finditer(line): + owner = next((pin for j, pin in number_lines if j >= i), None) + if owner is None: + raise SystemExit(f"GPIO pad {m.group(0)!r} after the last pin") + pins[owner].append((int(m.group(1)), int(m.group(2)))) + order = [pin for _, pin in number_lines] + return pins, order + + +def parse_figure(section_lines): + """GPIO pads named anywhere in the section's pin assignment figure.""" + pads = set() + for line in section_lines: + if FIGURE_END_RE.match(line): + break + pads.update(PAD_RE.finditer(line)) + return {(int(m.group(1)), int(m.group(2))) for m in pads} + + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument("datasheet", type=Path, help="pdftotext -layout dump") + parser.add_argument("--section", required=True, help="e.g. 10.1.4") + parser.add_argument("--name", required=True, help="package map name, e.g. nrf54l15_qfn48") + parser.add_argument( + "--socs", required=True, help="comma-separated Kconfig SoC symbols the map applies to" + ) + parser.add_argument( + "--pins", type=int, required=True, help="expected number of package pins (or balls)" + ) + parser.add_argument( + "--rows", + help="ball row letters, e.g. ABCDEFGHJK (comma-separated A,B,...,AA " + "when the grid has two-letter rows), for ball grid packages; " + "pins are numbered sequentially in row-major order", + ) + parser.add_argument("--cols", type=int, default=10, help="ball grid columns (with --rows)") + parser.add_argument( + "--die-pad", + action="store_true", + help="the table lists the exposed die pad as one pin past --pins; " + "it is bonded to VSS and never routable", + ) + parser.add_argument( + "--source", required=True, help="datasheet file name for the header comment" + ) + parser.add_argument("--out", type=Path, required=True, help="output TOML path") + args = parser.parse_args() + + # Ball row letters: comma-separated when the grid has two-letter rows. + rows = None + if args.rows: + rows = args.rows.split(",") if "," in args.rows else list(args.rows) + + lines = args.datasheet.read_text().splitlines() + title, code, kind, section_lines = find_section(lines, args.section) + + pins, order = parse_table(section_lines, rows=rows, cols=args.cols) + expected_max = args.pins + (1 if args.die_pad else 0) + if len(order) != expected_max: + raise SystemExit(f"parsed {len(order)} pins, expected {expected_max}") + if args.die_pad: + die_pad = order[-1] + if pins[die_pad]: + raise SystemExit(f"die pad {die_pad} unexpectedly bonded to GPIO {pins[die_pad]}") + del pins[die_pad] + order = order[:-1] + if not args.rows: + missing = [n for n in range(1, args.pins + 1) if n not in pins] + if missing: + raise SystemExit(f"pin numbers not found: {missing}") + + # Every pin must bond at most one GPIO pad, and the set of bonded pads + # must match the pads the figure shows for this package. + multi = {n: p for n, p in pins.items() if len(p) > 1} + if multi: + raise SystemExit(f"pins with more than one GPIO pad: {multi}") + table_pads = {p[0] for p in pins.values() if p} + figure_pads = parse_figure(section_lines) + if not figure_pads: + # CSP/BGA figures are unlabeled ball grids; there is nothing to + # cross-check against. The pad-count sanity check below still applies. + print("note: figure names no GPIO pads; skipping table/figure cross-check") + elif table_pads != figure_pads: + raise SystemExit( + "pad mismatch table vs figure:\n" + f" only in table: {sorted(table_pads - figure_pads)}\n" + f" only in figure: {sorted(figure_pads - table_pads)}" + ) + + skipped = [pin for pin in order if not pins[pin]] + + def pin_label(pin): + return f"{pin[0]}{pin[1]}" if isinstance(pin, tuple) else str(pin) + + header_notes = [] + if args.rows: + header_notes += [ + "# Ball packages: 'pin' numbers the balls sequentially in row-major", + "# order (the datasheet only labels them, e.g. B2).", + "", + ] + out_lines = [ + f"# Package pin map for {args.name}, transcribed from {args.source},", + f"# section {args.section} ({title}{f', {code}' if code else ''} {kind}),", + "# by tools/gen_package.py. Review against the datasheet figure before", + "# editing by hand; regenerate instead when the datasheet changes.", + "", + *header_notes, + f'name = "{args.name}"', + f"socs = [{', '.join(f'"{s}"' for s in args.socs.split(','))}]", + "", + ] + for number, pin in enumerate(order, start=1): + pad = pins[pin][0] if pins[pin] else None + if not pad: + continue + soc_pad = pad[0] * 32 + pad[1] + out_lines.append("[[pins]]") + out_lines.append(f"pin = {number}") + if isinstance(pin, tuple): + out_lines.append(f'ball = "{pin[0]}{pin[1]}"') + out_lines.append(f"pad = {soc_pad}") + out_lines.append(f'pad_name = "P{pad[0]}.{pad[1]:02d}"') + out_lines.append("") + if skipped: + out_lines.append("# Package pins without a GPIO pad (not routable):") + out_lines.append("# " + ", ".join(pin_label(pin) for pin in skipped)) + out_lines.append("") + + args.out.write_text("\n".join(out_lines)) + + print( + f"{args.out}: {sum(1 for p in pins.values() if p)} GPIO pads " + f"on {len(order)} package pins, {len(skipped)} skipped" + ) + for number, pin in enumerate(order, start=1): + pad = pins[pin][0] if pins[pin] else None + if pad: + print(f" pin {pin_label(pin):>4} -> P{pad[0]}.{pad[1]:02d}") + + +if __name__ == "__main__": + main() diff --git a/ports/zephyr-cp/modules/iobroker/tools/gen_package_c.py b/ports/zephyr-cp/modules/iobroker/tools/gen_package_c.py new file mode 100644 index 00000000000..1ac0feab7d3 --- /dev/null +++ b/ports/zephyr-cp/modules/iobroker/tools/gen_package_c.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Render a iobroker package TOML as the C pin map translation unit. + +Run at build time by the module's CMakeLists.txt for the package selected via +CONFIG_IOBROKER_PACKAGE_*; the output lands in the build directory, so +nothing generated is committed. The declarations live in iobroker.h. + +Usage: + gen_package_c.py packages/nrf54l15_qfn48.toml package_pins.c +""" + +import sys +import tomllib +from pathlib import Path + + +def main(): + if len(sys.argv) != 3: + raise SystemExit(__doc__) + toml_path, out_path = Path(sys.argv[1]), Path(sys.argv[2]) + with toml_path.open("rb") as f: + data = tomllib.load(f) + + lines = [ + f"// Generated from {toml_path.name} by tools/gen_package_c.py -- do not edit.", + f"// Package pin map '{data['name']}' for {', '.join(data['socs'])}.", + "", + "#include ", + "#include ", + "", + "#include ", + "", + "const iobroker_package_pin_t iobroker_package_pins[] = {", + ] + for entry in data["pins"]: + if "pad" not in entry: + # Non-GPIO pins (mounting, power, etc.) carry no SoC pad. + continue + ball = f" ({entry['ball']})" if "ball" in entry else "" + pad_name = f" {entry['pad_name']}" if "pad_name" in entry else "" + comment = f" //{ball}{pad_name}" + lines.append(f" {{ PACKAGE_PIN({entry['pin']}), {entry['pad']} }},{comment}") + lines += [ + "};", + f"const size_t iobroker_package_pin_count = {len(data['pins'])};", + "", + ] + out_path.write_text("\n".join(lines)) + + +if __name__ == "__main__": + main() diff --git a/ports/zephyr-cp/prj.conf b/ports/zephyr-cp/prj.conf index 80a28a9c272..6b480c373a0 100644 --- a/ports/zephyr-cp/prj.conf +++ b/ports/zephyr-cp/prj.conf @@ -38,6 +38,17 @@ CONFIG_EVENTS=y CONFIG_SERIAL=y +# Fail quickly on hung I2C devices (probe/scan also uses this timeout) +CONFIG_I2C_TRANSFER_TIMEOUT_MS=50 + +# Zephyr's Nordic SoC defconfig sets UART_USE_RUNTIME_CONFIGURE default n +# (soc/nordic/Kconfig.defconfig) for footprint. Without it the UARTE driver +# does not register api.configure and uart_configure() returns -ENOSYS +# unconditionally, which makes busio.UART fail with "Unsupported UART +# configuration" for every config. Runtime configure is required for the +# iobroker-routed UARTs to get their baudrate/parity/stop bits applied. +CONFIG_UART_USE_RUNTIME_CONFIGURE=y + CONFIG_LOG=y CONFIG_LOG_MAX_LEVEL=2 CONFIG_FRAME_POINTER=n diff --git a/ports/zephyr-cp/socs/nrf52840.conf b/ports/zephyr-cp/socs/nrf52840.conf index bf70997d83f..734d26db8e9 100644 --- a/ports/zephyr-cp/socs/nrf52840.conf +++ b/ports/zephyr-cp/socs/nrf52840.conf @@ -1,4 +1,3 @@ -CONFIG_NRFX_UARTE0=y -CONFIG_NRFX_UARTE1=y +CONFIG_NRFX_UARTE=y CONFIG_NRFX_POWER=y diff --git a/ports/zephyr-cp/zephyr-config/west.yml b/ports/zephyr-cp/zephyr-config/west.yml index 73b87efd85e..5830910c27d 100644 --- a/ports/zephyr-cp/zephyr-config/west.yml +++ b/ports/zephyr-cp/zephyr-config/west.yml @@ -10,7 +10,7 @@ manifest: path: modules/bsim_hw_models/nrf_hw_models - name: zephyr url: https://github.com/adafruit/zephyr - revision: 52dc937c7cda06a1c18ff6adec281bbeb096b3d4 + revision: db2dd2aa0914b9b427baa6a3df3e6460699e4c80 clone-depth: 100 import: # Skip what no CircuitPython board can use, so west update fetches 37