From da50dfa686401f3b0c13bb879844ca3d36c21705 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 2 Dec 2025 09:52:57 -0800 Subject: [PATCH 1/3] Add I2C support to the Zephyr builds You cannot instantiate `zephyr_i2c.I2C()` because they are fixed by the board's device tree. All busses with status = "okay" are added to board with all of their labels as names. So, I2C() may be the same as I2C1() and ARDUINO_I2C(). Part of #9903. --- locale/circuitpython.pot | 14 +- ports/zephyr-cp/bindings/zephyr_i2c/I2C.c | 378 ++++++++++++++++++ ports/zephyr-cp/bindings/zephyr_i2c/I2C.h | 32 ++ .../zephyr-cp/bindings/zephyr_i2c/__init__.c | 27 ++ ports/zephyr-cp/boards/board_aliases.cmake | 2 + .../nordic/nrf5340dk/autogen_board_info.toml | 3 +- .../nordic/nrf54h20dk/autogen_board_info.toml | 116 ++++++ .../nordic/nrf54h20dk/circuitpython.toml | 1 + .../nordic/nrf54l15dk/autogen_board_info.toml | 3 +- .../nordic/nrf7002dk/autogen_board_info.toml | 3 +- .../boards/nrf54h20dk_nrf54h20_cpuapp.conf | 1 + .../boards/nrf54h20dk_nrf54h20_cpuapp.overlay | 45 +++ .../zephyr-cp/boards/nucleo_n657x0_q.overlay | 0 .../renesas/ek_ra6m5/autogen_board_info.toml | 3 +- .../renesas/ek_ra8d1/autogen_board_info.toml | 3 +- .../nucleo_n657x0_q/autogen_board_info.toml | 116 ++++++ .../st/nucleo_n657x0_q/circuitpython.toml | 1 + .../nucleo_u575zi_q/autogen_board_info.toml | 3 +- .../st/stm32h7b3i_dk/autogen_board_info.toml | 3 +- ports/zephyr-cp/common-hal/zephyr_i2c/I2C.c | 142 ++++--- ports/zephyr-cp/common-hal/zephyr_i2c/I2C.h | 12 +- .../zephyr-cp/common-hal/zephyr_serial/UART.c | 6 + .../zephyr-cp/common-hal/zephyr_serial/UART.h | 2 + .../zephyr-cp/cptools/build_circuitpython.py | 12 +- ports/zephyr-cp/cptools/zephyr2cp.py | 123 +++++- ports/zephyr-cp/prj.conf | 2 + ports/zephyr-cp/supervisor/flash.c | 4 + 27 files changed, 975 insertions(+), 82 deletions(-) create mode 100644 ports/zephyr-cp/bindings/zephyr_i2c/I2C.c create mode 100644 ports/zephyr-cp/bindings/zephyr_i2c/I2C.h create mode 100644 ports/zephyr-cp/bindings/zephyr_i2c/__init__.c create mode 100644 ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml create mode 100644 ports/zephyr-cp/boards/nordic/nrf54h20dk/circuitpython.toml create mode 100644 ports/zephyr-cp/boards/nrf54h20dk_nrf54h20_cpuapp.conf create mode 100644 ports/zephyr-cp/boards/nrf54h20dk_nrf54h20_cpuapp.overlay create mode 100644 ports/zephyr-cp/boards/nucleo_n657x0_q.overlay create mode 100644 ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml create mode 100644 ports/zephyr-cp/boards/st/nucleo_n657x0_q/circuitpython.toml diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 35fc94513a547..81074f3f80464 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -111,6 +111,7 @@ msgid "%q in %q must be of type %q, not %q" msgstr "" #: ports/espressif/common-hal/espulp/ULP.c +#: ports/espressif/common-hal/mipidsi/Bus.c #: ports/mimxrt10xx/common-hal/audiobusio/__init__.c #: ports/mimxrt10xx/common-hal/usb_host/Port.c #: ports/raspberrypi/common-hal/picodvi/Framebuffer_RP2040.c @@ -244,7 +245,8 @@ msgstr "" #: ports/nordic/common-hal/pulseio/PulseIn.c #: ports/raspberrypi/common-hal/rp2pio/StateMachine.c #: ports/stm/common-hal/pulseio/PulseIn.c py/argcheck.c -#: shared-bindings/canio/Match.c shared-bindings/time/__init__.c +#: shared-bindings/bitmaptools/__init__.c shared-bindings/canio/Match.c +#: shared-bindings/time/__init__.c msgid "%q out of range" msgstr "" @@ -834,10 +836,6 @@ msgstr "" msgid "Clock unit in use" msgstr "" -#: ports/espressif/common-hal/mipidsi/Display.c -msgid "Color depth must be 16 or 24" -msgstr "" - #: shared-bindings/_bleio/Connection.c msgid "" "Connection has been disconnected and can no longer be used. Create a new " @@ -1119,8 +1117,9 @@ msgid "" "Frequency must be 24, 150, 396, 450, 528, 600, 720, 816, 912, 960 or 1008 Mhz" msgstr "" -#: shared-bindings/bitbangio/I2C.c shared-bindings/bitbangio/SPI.c -#: shared-bindings/busio/I2C.c shared-bindings/busio/SPI.c +#: ports/zephyr-cp/bindings/zephyr_i2c/I2C.c shared-bindings/bitbangio/I2C.c +#: shared-bindings/bitbangio/SPI.c shared-bindings/busio/I2C.c +#: shared-bindings/busio/SPI.c msgid "Function requires lock" msgstr "" @@ -1271,6 +1270,7 @@ msgstr "" #: ports/espressif/common-hal/_bleio/Service.c #: ports/espressif/common-hal/espulp/ULP.c #: ports/espressif/common-hal/microcontroller/Processor.c +#: ports/espressif/common-hal/mipidsi/Display.c #: ports/mimxrt10xx/common-hal/audiobusio/__init__.c #: ports/mimxrt10xx/common-hal/pwmio/PWMOut.c #: ports/raspberrypi/bindings/picodvi/Framebuffer.c diff --git a/ports/zephyr-cp/bindings/zephyr_i2c/I2C.c b/ports/zephyr-cp/bindings/zephyr_i2c/I2C.c new file mode 100644 index 0000000000000..08ce9e966a669 --- /dev/null +++ b/ports/zephyr-cp/bindings/zephyr_i2c/I2C.c @@ -0,0 +1,378 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "bindings/zephyr_i2c/I2C.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/util.h" +#include "shared/runtime/buffer_helper.h" +#include "py/binary.h" +#include "py/mperrno.h" +#include "py/runtime.h" + +#include +#include + +//| class I2C: +//| """Two wire serial protocol +//| +//| I2C is a two-wire protocol for communicating between devices. At the +//| physical level it consists of 2 wires: SCL and SDA, the clock and data +//| lines respectively. +//| +//| .. class:: I2C() +//| +//| Cannot be instantiated directly. Instead singletons are created using the +//| `board` aliases that match the device tree labels. `board` may list multiple +//| aliases for a single device. For example, `board.I2C1` and `board.ARDUINO_I2C` +//| may both refer to the same device. +//| """ +//| + +static zephyr_i2c_i2c_obj_t *native_i2c(mp_obj_t self_in) { + mp_check_self(mp_obj_is_type(self_in, &zephyr_i2c_i2c_type)); + return MP_OBJ_TO_PTR(self_in); +} + +static void check_for_deinit(zephyr_i2c_i2c_obj_t *self) { + if (common_hal_zephyr_i2c_i2c_deinited(self)) { + raise_deinited_error(); + } +} + +static void check_lock(zephyr_i2c_i2c_obj_t *self) { + if (!common_hal_zephyr_i2c_i2c_has_lock(self)) { + mp_raise_RuntimeError(MP_ERROR_TEXT("Function requires lock")); + } +} + +//| def deinit(self) -> None: +//| """Releases control of the underlying hardware so other classes can use it.""" +//| ... +static mp_obj_t zephyr_i2c_i2c_obj_deinit(mp_obj_t self_in) { + zephyr_i2c_i2c_obj_t *self = native_i2c(self_in); + common_hal_zephyr_i2c_i2c_deinit(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(zephyr_i2c_i2c_deinit_obj, zephyr_i2c_i2c_obj_deinit); + +//| def __enter__(self) -> I2C: +//| """No-op used in Context Managers.""" +//| ... +static mp_obj_t zephyr_i2c_i2c_obj___enter__(mp_obj_t self_in) { + zephyr_i2c_i2c_obj_t *self = native_i2c(self_in); + check_for_deinit(self); + return self_in; +} +static MP_DEFINE_CONST_FUN_OBJ_1(zephyr_i2c_i2c___enter___obj, zephyr_i2c_i2c_obj___enter__); + +//| def __exit__(self) -> None: +//| """Automatically deinitializes the hardware on context exit.""" +//| ... +static mp_obj_t zephyr_i2c_i2c_obj___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + common_hal_zephyr_i2c_i2c_deinit(args[0]); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(zephyr_i2c_i2c___exit___obj, 4, 4, zephyr_i2c_i2c_obj___exit__); + +//| def probe(self, address: int) -> bool: +//| """Check if a device responds at the address. +//| +//| :param int address: The 7-bit device address +//| :return: True if the device responds, False otherwise +//| :rtype: bool +//| """ +//| ... +static mp_obj_t zephyr_i2c_i2c_probe(mp_obj_t self_in, mp_obj_t addr_obj) { + zephyr_i2c_i2c_obj_t *self = native_i2c(self_in); + check_for_deinit(self); + check_lock(self); + + mp_int_t addr = mp_obj_get_int(addr_obj); + return mp_obj_new_bool(common_hal_zephyr_i2c_i2c_probe(self, addr)); +} +static MP_DEFINE_CONST_FUN_OBJ_2(zephyr_i2c_i2c_probe_obj, zephyr_i2c_i2c_probe); + +//| def scan(self) -> List[int]: +//| """Scan all I2C addresses between 0x08 and 0x77 inclusive and return a +//| list of those that respond. +//| +//| :return: List of device addresses +//| :rtype: list +//| """ +//| ... +static mp_obj_t zephyr_i2c_i2c_scan(mp_obj_t self_in) { + zephyr_i2c_i2c_obj_t *self = native_i2c(self_in); + check_for_deinit(self); + check_lock(self); + + mp_obj_t list = mp_obj_new_list(0, NULL); + // 7-bit addresses 0b0000xxx and 0b1111xxx are reserved + for (uint8_t addr = 0x08; addr <= 0x77; addr++) { + if (common_hal_zephyr_i2c_i2c_probe(self, addr)) { + mp_obj_list_append(list, MP_OBJ_NEW_SMALL_INT(addr)); + } + } + return list; +} +static MP_DEFINE_CONST_FUN_OBJ_1(zephyr_i2c_i2c_scan_obj, zephyr_i2c_i2c_scan); + +//| def try_lock(self) -> bool: +//| """Attempts to grab the I2C lock. Returns True on success. +//| +//| :return: True when lock has been grabbed +//| :rtype: bool +//| """ +//| ... +static mp_obj_t zephyr_i2c_i2c_obj_try_lock(mp_obj_t self_in) { + zephyr_i2c_i2c_obj_t *self = native_i2c(self_in); + check_for_deinit(self); + return mp_obj_new_bool(common_hal_zephyr_i2c_i2c_try_lock(self)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(zephyr_i2c_i2c_try_lock_obj, zephyr_i2c_i2c_obj_try_lock); + +//| def unlock(self) -> None: +//| """Releases the I2C lock.""" +//| ... +static mp_obj_t zephyr_i2c_i2c_obj_unlock(mp_obj_t self_in) { + zephyr_i2c_i2c_obj_t *self = native_i2c(self_in); + check_for_deinit(self); + common_hal_zephyr_i2c_i2c_unlock(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(zephyr_i2c_i2c_unlock_obj, zephyr_i2c_i2c_obj_unlock); + +//| import sys +//| +//| def readfrom_into( +//| self, address: int, buffer: WriteableBuffer, *, start: int = 0, end: int = sys.maxsize +//| ) -> None: +//| """Read into ``buffer`` from the device selected by ``address``. +//| At least one byte must be read. +//| +//| If ``start`` or ``end`` is provided, then the buffer will be sliced +//| as if ``buffer[start:end]`` were passed, but without copying the data. +//| The number of bytes read will be the length of ``buffer[start:end]``. +//| +//| :param int address: 7-bit device address +//| :param ~circuitpython_typing.WriteableBuffer buffer: buffer to write into +//| :param int start: beginning of buffer slice +//| :param int end: end of buffer slice; if not specified, use ``len(buffer)`` +//| """ +//| ... +static mp_obj_t zephyr_i2c_i2c_readfrom_into(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_address, ARG_buffer, ARG_start, ARG_end }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_address, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} }, + }; + zephyr_i2c_i2c_obj_t *self = native_i2c(pos_args[0]); + check_for_deinit(self); + check_lock(self); + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_WRITE); + + // Compute bounds in terms of elements, not bytes. + int stride_in_bytes = mp_binary_get_size('@', bufinfo.typecode, NULL); + size_t length = bufinfo.len / stride_in_bytes; + int32_t start = args[ARG_start].u_int; + const int32_t end = args[ARG_end].u_int; + normalize_buffer_bounds(&start, end, &length); + mp_arg_validate_length_min(length, 1, MP_QSTR_buffer); + + // Treat start and length in terms of bytes from now on. + start *= stride_in_bytes; + length *= stride_in_bytes; + + uint8_t status = common_hal_zephyr_i2c_i2c_read(self, args[ARG_address].u_int, + ((uint8_t *)bufinfo.buf) + start, length); + + if (status != 0) { + mp_raise_OSError(status); + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(zephyr_i2c_i2c_readfrom_into_obj, 3, zephyr_i2c_i2c_readfrom_into); + +//| import sys +//| +//| def writeto( +//| self, address: int, buffer: ReadableBuffer, *, start: int = 0, end: int = sys.maxsize +//| ) -> None: +//| """Write the bytes from ``buffer`` to the device selected by ``address`` and +//| then transmit a stop bit. +//| +//| If ``start`` or ``end`` is provided, then the buffer will be sliced +//| as if ``buffer[start:end]`` were passed, but without copying the data. +//| The number of bytes written will be the length of ``buffer[start:end]``. +//| +//| Writing a buffer or slice of length zero is permitted, as it can be used +//| to poll for the existence of a device. +//| +//| :param int address: 7-bit device address +//| :param ~circuitpython_typing.ReadableBuffer buffer: buffer containing the bytes to write +//| :param int start: beginning of buffer slice +//| :param int end: end of buffer slice; if not specified, use ``len(buffer)`` +//| """ +//| ... +static mp_obj_t zephyr_i2c_i2c_writeto(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_address, ARG_buffer, ARG_start, ARG_end }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_address, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} }, + }; + zephyr_i2c_i2c_obj_t *self = native_i2c(pos_args[0]); + check_for_deinit(self); + check_lock(self); + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // get the buffer to write the data from + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_READ); + int stride_in_bytes = mp_binary_get_size('@', bufinfo.typecode, NULL); + + // Compute bounds in terms of elements, not bytes. + size_t length = bufinfo.len / stride_in_bytes; + int32_t start = args[ARG_start].u_int; + const int32_t end = args[ARG_end].u_int; + normalize_buffer_bounds(&start, end, &length); + + // Treat start and length in terms of bytes from now on. + start *= stride_in_bytes; + length *= stride_in_bytes; + + uint8_t status = common_hal_zephyr_i2c_i2c_write(self, args[ARG_address].u_int, + ((uint8_t *)bufinfo.buf) + start, length); + + if (status != 0) { + mp_raise_OSError(status); + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(zephyr_i2c_i2c_writeto_obj, 3, zephyr_i2c_i2c_writeto); + +//| import sys +//| +//| def writeto_then_readfrom( +//| self, +//| address: int, +//| out_buffer: ReadableBuffer, +//| in_buffer: WriteableBuffer, +//| *, +//| out_start: int = 0, +//| out_end: int = None, +//| in_start: int = 0, +//| in_end: int = None +//| ) -> None: +//| """Write the bytes from ``out_buffer`` to the device selected by ``address``, generate +//| no stop bit, generate a repeated start and read into ``in_buffer``. ``out_buffer`` and +//| ``in_buffer`` can be the same buffer because they are used sequentially. +//| +//| If ``out_start`` or ``out_end`` is provided, then the buffer will be sliced +//| as if ``out_buffer[out_start:out_end]`` were passed, but without copying the data. +//| The number of bytes written will be the length of ``out_buffer[start:end]``. +//| +//| If ``in_start`` or ``in_end`` is provided, then the input buffer will be sliced +//| as if ``in_buffer[in_start:in_end]`` were passed, +//| The number of bytes read will be the length of ``out_buffer[in_start:in_end]``. +//| +//| :param int address: 7-bit device address +//| :param ~circuitpython_typing.ReadableBuffer out_buffer: buffer containing the bytes to write +//| :param ~circuitpython_typing.WriteableBuffer in_buffer: buffer to write into +//| :param int out_start: beginning of ``out_buffer`` slice +//| :param int out_end: end of ``out_buffer`` slice; if not specified, use ``len(out_buffer)`` +//| :param int in_start: beginning of ``in_buffer`` slice +//| :param int in_end: end of ``in_buffer`` slice; if not specified, use ``len(in_buffer)`` +//| """ +//| ... +static mp_obj_t zephyr_i2c_i2c_writeto_then_readfrom(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_address, ARG_out_buffer, ARG_in_buffer, ARG_out_start, ARG_out_end, ARG_in_start, ARG_in_end }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_address, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_out_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_in_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_out_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_out_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} }, + { MP_QSTR_in_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_in_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} }, + }; + zephyr_i2c_i2c_obj_t *self = native_i2c(pos_args[0]); + check_for_deinit(self); + check_lock(self); + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + mp_buffer_info_t out_bufinfo; + mp_get_buffer_raise(args[ARG_out_buffer].u_obj, &out_bufinfo, MP_BUFFER_READ); + int out_stride_in_bytes = mp_binary_get_size('@', out_bufinfo.typecode, NULL); + size_t out_length = out_bufinfo.len / out_stride_in_bytes; + int32_t out_start = args[ARG_out_start].u_int; + const int32_t out_end = args[ARG_out_end].u_int; + normalize_buffer_bounds(&out_start, out_end, &out_length); + + mp_buffer_info_t in_bufinfo; + mp_get_buffer_raise(args[ARG_in_buffer].u_obj, &in_bufinfo, MP_BUFFER_WRITE); + int in_stride_in_bytes = mp_binary_get_size('@', in_bufinfo.typecode, NULL); + size_t in_length = in_bufinfo.len / in_stride_in_bytes; + int32_t in_start = args[ARG_in_start].u_int; + const int32_t in_end = args[ARG_in_end].u_int; + normalize_buffer_bounds(&in_start, in_end, &in_length); + mp_arg_validate_length_min(in_length, 1, MP_QSTR_out_buffer); + + // Treat start and length in terms of bytes from now on. + out_start *= out_stride_in_bytes; + out_length *= out_stride_in_bytes; + in_start *= in_stride_in_bytes; + in_length *= in_stride_in_bytes; + + uint8_t status = common_hal_zephyr_i2c_i2c_write_read(self, args[ARG_address].u_int, + ((uint8_t *)out_bufinfo.buf) + out_start, out_length, + ((uint8_t *)in_bufinfo.buf) + in_start, in_length); + + if (status != 0) { + mp_raise_OSError(status); + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(zephyr_i2c_i2c_writeto_then_readfrom_obj, 4, zephyr_i2c_i2c_writeto_then_readfrom); + +static const mp_rom_map_elem_t zephyr_i2c_i2c_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&zephyr_i2c_i2c_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&zephyr_i2c_i2c_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&zephyr_i2c_i2c___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&zephyr_i2c_i2c___exit___obj) }, + { MP_ROM_QSTR(MP_QSTR_probe), MP_ROM_PTR(&zephyr_i2c_i2c_probe_obj) }, + { MP_ROM_QSTR(MP_QSTR_scan), MP_ROM_PTR(&zephyr_i2c_i2c_scan_obj) }, + + { MP_ROM_QSTR(MP_QSTR_try_lock), MP_ROM_PTR(&zephyr_i2c_i2c_try_lock_obj) }, + { MP_ROM_QSTR(MP_QSTR_unlock), MP_ROM_PTR(&zephyr_i2c_i2c_unlock_obj) }, + + { MP_ROM_QSTR(MP_QSTR_readfrom_into), MP_ROM_PTR(&zephyr_i2c_i2c_readfrom_into_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeto), MP_ROM_PTR(&zephyr_i2c_i2c_writeto_obj) }, + { MP_ROM_QSTR(MP_QSTR_writeto_then_readfrom), MP_ROM_PTR(&zephyr_i2c_i2c_writeto_then_readfrom_obj) }, +}; +static MP_DEFINE_CONST_DICT(zephyr_i2c_i2c_locals_dict, zephyr_i2c_i2c_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + zephyr_i2c_i2c_type, + MP_QSTR_I2C, + MP_TYPE_FLAG_NONE, + locals_dict, &zephyr_i2c_i2c_locals_dict + ); diff --git a/ports/zephyr-cp/bindings/zephyr_i2c/I2C.h b/ports/zephyr-cp/bindings/zephyr_i2c/I2C.h new file mode 100644 index 0000000000000..7f7eecbea6c62 --- /dev/null +++ b/ports/zephyr-cp/bindings/zephyr_i2c/I2C.h @@ -0,0 +1,32 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include "py/obj.h" +#include "common-hal/zephyr_i2c/I2C.h" + +extern const mp_obj_type_t zephyr_i2c_i2c_type; + +// Check if the I2C object has been deinitialized +bool common_hal_zephyr_i2c_i2c_deinited(zephyr_i2c_i2c_obj_t *self); + +// Deinitialize the I2C bus +void common_hal_zephyr_i2c_i2c_deinit(zephyr_i2c_i2c_obj_t *self); + +// Locking functions +bool common_hal_zephyr_i2c_i2c_try_lock(zephyr_i2c_i2c_obj_t *self); +bool common_hal_zephyr_i2c_i2c_has_lock(zephyr_i2c_i2c_obj_t *self); +void common_hal_zephyr_i2c_i2c_unlock(zephyr_i2c_i2c_obj_t *self); + +// Device communication functions +bool common_hal_zephyr_i2c_i2c_probe(zephyr_i2c_i2c_obj_t *self, uint8_t addr); +uint8_t common_hal_zephyr_i2c_i2c_write(zephyr_i2c_i2c_obj_t *self, uint16_t address, + const uint8_t *data, size_t len); +uint8_t common_hal_zephyr_i2c_i2c_read(zephyr_i2c_i2c_obj_t *self, uint16_t address, + uint8_t *data, size_t len); +uint8_t common_hal_zephyr_i2c_i2c_write_read(zephyr_i2c_i2c_obj_t *self, uint16_t address, + uint8_t *out_data, size_t out_len, uint8_t *in_data, size_t in_len); diff --git a/ports/zephyr-cp/bindings/zephyr_i2c/__init__.c b/ports/zephyr-cp/bindings/zephyr_i2c/__init__.c new file mode 100644 index 0000000000000..3274dfdb74099 --- /dev/null +++ b/ports/zephyr-cp/bindings/zephyr_i2c/__init__.c @@ -0,0 +1,27 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include "py/obj.h" +#include "py/runtime.h" +#include "bindings/zephyr_i2c/I2C.h" + +//| """Zephyr I2C driver for fixed I2C busses. +//| +//| This module provides access to I2C busses using Zephyr device labels.""" + +static const mp_rom_map_elem_t zephyr_i2c_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_zephyr_i2c) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&zephyr_i2c_i2c_type) }, +}; + +static MP_DEFINE_CONST_DICT(zephyr_i2c_module_globals, zephyr_i2c_module_globals_table); + +const mp_obj_module_t zephyr_i2c_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&zephyr_i2c_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_zephyr_i2c, zephyr_i2c_module); diff --git a/ports/zephyr-cp/boards/board_aliases.cmake b/ports/zephyr-cp/boards/board_aliases.cmake index b8df9fb87f5b8..bb400f188dbe9 100644 --- a/ports/zephyr-cp/boards/board_aliases.cmake +++ b/ports/zephyr-cp/boards/board_aliases.cmake @@ -2,7 +2,9 @@ set(pca10056_BOARD_ALIAS nrf52840dk/nrf52840) set(renesas_ek_ra6m5_BOARD_ALIAS ek_ra6m5) set(renesas_ek_ra8d1_BOARD_ALIAS ek_ra8d1) set(nordic_nrf54l15dk_BOARD_ALIAS nrf54l15dk/nrf54l15/cpuapp) +set(nordic_nrf54h20dk_BOARD_ALIAS nrf54h20dk/nrf54h20/cpuapp) set(nordic_nrf5340dk_BOARD_ALIAS nrf5340dk/nrf5340/cpuapp) set(nordic_nrf7002dk_BOARD_ALIAS nrf7002dk/nrf5340/cpuapp) set(st_stm32h7b3i_dk_BOARD_ALIAS stm32h7b3i_dk) set(st_nucleo_u575zi_q_BOARD_ALIAS nucleo_u575zi_q/stm32u575xx) +set(st_nucleo_n657x0_q_BOARD_ALIAS nucleo_n657x0_q/stm32n657xx) diff --git a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml index 35966d6f9e2c6..c59e03c323eb1 100644 --- a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml @@ -110,6 +110,7 @@ vectorio = false warnings = false watchdog = false wifi = false +zephyr_i2c = true # Zephyr board has zephyr_i2c zephyr_kernel = false -zephyr_serial = true +zephyr_serial = true # Zephyr board has zephyr_serial zlib = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml new file mode 100644 index 0000000000000..d4017cae4dad0 --- /dev/null +++ b/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml @@ -0,0 +1,116 @@ +# This file is autogenerated when a board is built. Do not edit. Do commit it to git. Other scripts use its info. +name = "Nordic Semiconductor nRF54H20 DK" + +[modules] +__future__ = false +_bleio = false +_eve = false +_pew = false +_pixelmap = false +_stage = false +adafruit_bus_device = false +adafruit_pixelbuf = false +aesio = false +alarm = false +analogbufio = false +analogio = false +atexit = false +audiobusio = false +audiocore = false +audiodelays = false +audiofilters = false +audiofreeverb = false +audioio = false +audiomixer = false +audiomp3 = false +audiopwmio = false +aurora_epaper = false +bitbangio = false +bitmapfilter = false +bitmaptools = false +bitops = false +board = false +busdisplay = false +busio = false +camera = false +canio = false +codeop = false +countio = false +digitalio = true +displayio = false +dotclockframebuffer = false +dualbank = false +epaperdisplay = false +floppyio = false +fontio = false +fourwire = false +framebufferio = false +frequencyio = false +getpass = false +gifio = false +gnss = false +hashlib = false +i2cdisplaybus = false +i2ctarget = false +imagecapture = false +ipaddress = false +is31fl3741 = false +jpegio = false +keypad = false +keypad_demux = false +locale = false +lvfontio = false +math = false +max3421e = false +mdns = false +memorymap = false +memorymonitor = false +microcontroller = true +mipidsi = false +msgpack = false +neopixel_write = false +nvm = false +onewireio = false +os = true +paralleldisplaybus = false +ps2io = false +pulseio = false +pwmio = false +qrio = false +rainbowio = false +random = true +rclcpy = false +rgbmatrix = false +rotaryio = false +rtc = false +sdcardio = false +sdioio = false +sharpdisplay = false +socketpool = false +spitarget = false +ssl = false +storage = true # Zephyr board has flash +struct = true +supervisor = false +synthio = false +terminalio = false +tilepalettemapper = false +time = true +touchio = false +traceback = false +uheap = false +usb = false +usb_cdc = false +usb_hid = false +usb_host = false +usb_midi = false +usb_video = false +ustack = false +vectorio = false +warnings = false +watchdog = false +wifi = false +zephyr_i2c = true # Zephyr board has zephyr_i2c +zephyr_kernel = false +zephyr_serial = true # Zephyr board has zephyr_serial +zlib = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54h20dk/circuitpython.toml b/ports/zephyr-cp/boards/nordic/nrf54h20dk/circuitpython.toml new file mode 100644 index 0000000000000..3272dd4c5f319 --- /dev/null +++ b/ports/zephyr-cp/boards/nordic/nrf54h20dk/circuitpython.toml @@ -0,0 +1 @@ +CIRCUITPY_BUILD_EXTENSIONS = ["elf"] diff --git a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml index e3b6cc2248f2e..dbffa5944d96c 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54l15dk/autogen_board_info.toml @@ -110,6 +110,7 @@ vectorio = false warnings = false watchdog = false wifi = false +zephyr_i2c = false zephyr_kernel = false -zephyr_serial = true +zephyr_serial = true # Zephyr board has zephyr_serial zlib = false diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml index 328105d715059..7a3ea9267c9e9 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml @@ -110,6 +110,7 @@ vectorio = false warnings = false watchdog = false wifi = true # Zephyr board has wifi +zephyr_i2c = false zephyr_kernel = false -zephyr_serial = true +zephyr_serial = false zlib = false diff --git a/ports/zephyr-cp/boards/nrf54h20dk_nrf54h20_cpuapp.conf b/ports/zephyr-cp/boards/nrf54h20dk_nrf54h20_cpuapp.conf new file mode 100644 index 0000000000000..f7443ecfa33d4 --- /dev/null +++ b/ports/zephyr-cp/boards/nrf54h20dk_nrf54h20_cpuapp.conf @@ -0,0 +1 @@ +CONFIG_FLASH_MSPI_NOR_LAYOUT_PAGE_SIZE=4096 diff --git a/ports/zephyr-cp/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/ports/zephyr-cp/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 0000000000000..0d90c87931b61 --- /dev/null +++ b/ports/zephyr-cp/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,45 @@ +&gpio6 { + status = "okay"; + zephyr,pm-device-runtime-auto; +}; + +&exmif { + status = "okay"; + zephyr,pm-device-runtime-auto; +}; + +&mx25uw63 { + status = "okay"; +}; + +/* + * SDA = P2.11 + * SCL = P2.10 + */ + +&pinctrl { + i2c130_default: i2c130_default { + group1 { + psels = , + ; + }; + }; + + i2c130_sleep: i2c130_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; +}; + +&i2c130 { + clock-frequency = ; + pinctrl-0 = <&i2c130_default>; + pinctrl-1 = <&i2c130_sleep>; + pinctrl-names = "default", "sleep"; + zephyr,concat-buf-size = <256>; + memory-regions = <&cpuapp_dma_region>; + status = "okay"; +}; diff --git a/ports/zephyr-cp/boards/nucleo_n657x0_q.overlay b/ports/zephyr-cp/boards/nucleo_n657x0_q.overlay new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml index 2bc6e3e03a249..8c522296da6e3 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml @@ -110,6 +110,7 @@ vectorio = false warnings = false watchdog = false wifi = false +zephyr_i2c = true # Zephyr board has zephyr_i2c zephyr_kernel = false -zephyr_serial = true +zephyr_serial = true # Zephyr board has zephyr_serial zlib = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml index 2a2e16fd16afe..5c79b0e962efd 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml @@ -110,6 +110,7 @@ vectorio = false warnings = false watchdog = false wifi = false +zephyr_i2c = false zephyr_kernel = false -zephyr_serial = true +zephyr_serial = true # Zephyr board has zephyr_serial zlib = false diff --git a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml new file mode 100644 index 0000000000000..7a5beb79e442c --- /dev/null +++ b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml @@ -0,0 +1,116 @@ +# This file is autogenerated when a board is built. Do not edit. Do commit it to git. Other scripts use its info. +name = "STMicroelectronics Nucleo N657X0-Q" + +[modules] +__future__ = false +_bleio = false +_eve = false +_pew = false +_pixelmap = false +_stage = false +adafruit_bus_device = false +adafruit_pixelbuf = false +aesio = false +alarm = false +analogbufio = false +analogio = false +atexit = false +audiobusio = false +audiocore = false +audiodelays = false +audiofilters = false +audiofreeverb = false +audioio = false +audiomixer = false +audiomp3 = false +audiopwmio = false +aurora_epaper = false +bitbangio = false +bitmapfilter = false +bitmaptools = false +bitops = false +board = false +busdisplay = false +busio = false +camera = false +canio = false +codeop = false +countio = false +digitalio = true +displayio = false +dotclockframebuffer = false +dualbank = false +epaperdisplay = false +floppyio = false +fontio = false +fourwire = false +framebufferio = false +frequencyio = false +getpass = false +gifio = false +gnss = false +hashlib = false +i2cdisplaybus = false +i2ctarget = false +imagecapture = false +ipaddress = false +is31fl3741 = false +jpegio = false +keypad = false +keypad_demux = false +locale = false +lvfontio = false +math = false +max3421e = false +mdns = false +memorymap = false +memorymonitor = false +microcontroller = true +mipidsi = false +msgpack = false +neopixel_write = false +nvm = false +onewireio = false +os = true +paralleldisplaybus = false +ps2io = false +pulseio = false +pwmio = false +qrio = false +rainbowio = false +random = true +rclcpy = false +rgbmatrix = false +rotaryio = false +rtc = false +sdcardio = false +sdioio = false +sharpdisplay = false +socketpool = false +spitarget = false +ssl = false +storage = false +struct = true +supervisor = false +synthio = false +terminalio = false +tilepalettemapper = false +time = true +touchio = false +traceback = false +uheap = false +usb = false +usb_cdc = false # No TinyUSB settings for stm32n657xx +usb_hid = false +usb_host = false +usb_midi = false +usb_video = false +ustack = false +vectorio = false +warnings = false +watchdog = false +wifi = false +zephyr_i2c = true # Zephyr board has zephyr_i2c +zephyr_kernel = false +zephyr_serial = true # Zephyr board has zephyr_serial +zlib = false diff --git a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/circuitpython.toml b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/circuitpython.toml new file mode 100644 index 0000000000000..83e6bcd39c4f9 --- /dev/null +++ b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/circuitpython.toml @@ -0,0 +1 @@ +CIRCUITPY_BUILD_EXTENSIONS = ["hex"] diff --git a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml index 5042eb972b143..a3183f180aed6 100644 --- a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml @@ -110,6 +110,7 @@ vectorio = false warnings = false watchdog = false wifi = false +zephyr_i2c = true # Zephyr board has zephyr_i2c zephyr_kernel = false -zephyr_serial = true +zephyr_serial = true # Zephyr board has zephyr_serial zlib = false diff --git a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml index cfe48a90ceea4..bf6a282874349 100644 --- a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml @@ -110,6 +110,7 @@ vectorio = false warnings = false watchdog = false wifi = false +zephyr_i2c = true # Zephyr board has zephyr_i2c zephyr_kernel = false -zephyr_serial = true +zephyr_serial = true # Zephyr board has zephyr_serial zlib = false diff --git a/ports/zephyr-cp/common-hal/zephyr_i2c/I2C.c b/ports/zephyr-cp/common-hal/zephyr_i2c/I2C.c index d5bb0d4466912..12ce95c282d1c 100644 --- a/ports/zephyr-cp/common-hal/zephyr_i2c/I2C.c +++ b/ports/zephyr-cp/common-hal/zephyr_i2c/I2C.c @@ -1,92 +1,134 @@ // This file is part of the CircuitPython project: https://circuitpython.org // -// SPDX-FileCopyrightText: Copyright (c) 2019 Dan Halbert for Adafruit Industries -// SPDX-FileCopyrightText: Copyright (c) 2018 Artur Pacholec -// SPDX-FileCopyrightText: Copyright (c) 2017 hathach -// SPDX-FileCopyrightText: Copyright (c) 2016 Sandeep Mistry All right reserved. +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries // // SPDX-License-Identifier: MIT -#include "shared-bindings/busio/I2C.h" -#include "shared-bindings/microcontroller/__init__.h" -#include "shared-bindings/microcontroller/Pin.h" -#include "supervisor/shared/tick.h" +#include "bindings/zephyr_i2c/I2C.h" #include "py/mperrno.h" #include "py/runtime.h" +#include +#include +#include -void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) { - // never_reset_pin_number(self->scl_pin_number); - // never_reset_pin_number(self->sda_pin_number); +mp_obj_t zephyr_i2c_i2c_zephyr_init(zephyr_i2c_i2c_obj_t *self, const struct device *i2c_device) { + self->base.type = &zephyr_i2c_i2c_type; + self->i2c_device = i2c_device; + k_mutex_init(&self->mutex); + return MP_OBJ_FROM_PTR(self); } -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) { - -} - -bool common_hal_busio_i2c_deinited(busio_i2c_obj_t *self) { - // return self->sda_pin_number == NO_PIN; - return true; +bool common_hal_zephyr_i2c_i2c_deinited(zephyr_i2c_i2c_obj_t *self) { + // Always leave it active + return false; } -void common_hal_busio_i2c_deinit(busio_i2c_obj_t *self) { - if (common_hal_busio_i2c_deinited(self)) { +void common_hal_zephyr_i2c_i2c_deinit(zephyr_i2c_i2c_obj_t *self) { + if (common_hal_zephyr_i2c_i2c_deinited(self)) { return; } - - // nrfx_twim_uninit(&self->twim_peripheral->twim); - - // reset_pin_number(self->sda_pin_number); - // reset_pin_number(self->scl_pin_number); - - // self->twim_peripheral->in_use = false; - // common_hal_busio_i2c_mark_deinit(self); -} - -void common_hal_busio_i2c_mark_deinit(busio_i2c_obj_t *self) { - // self->sda_pin_number = NO_PIN; + // Always leave it active } -// nrfx_twim_tx doesn't support 0-length data so we fall back to the hal API -bool common_hal_busio_i2c_probe(busio_i2c_obj_t *self, uint8_t addr) { - bool found = true; +bool common_hal_zephyr_i2c_i2c_probe(zephyr_i2c_i2c_obj_t *self, uint8_t addr) { + if (common_hal_zephyr_i2c_i2c_deinited(self)) { + return false; + } - return found; + // Try a zero-length write to probe for device + uint8_t dummy; + int ret = i2c_write(self->i2c_device, &dummy, 0, addr); + return ret == 0; } -bool common_hal_busio_i2c_try_lock(busio_i2c_obj_t *self) { - if (common_hal_busio_i2c_deinited(self)) { +bool common_hal_zephyr_i2c_i2c_try_lock(zephyr_i2c_i2c_obj_t *self) { + if (common_hal_zephyr_i2c_i2c_deinited(self)) { return false; } - bool grabbed_lock = false; - return grabbed_lock; + + self->has_lock = k_mutex_lock(&self->mutex, K_NO_WAIT) == 0; + return self->has_lock; } -bool common_hal_busio_i2c_has_lock(busio_i2c_obj_t *self) { +bool common_hal_zephyr_i2c_i2c_has_lock(zephyr_i2c_i2c_obj_t *self) { return self->has_lock; } -void common_hal_busio_i2c_unlock(busio_i2c_obj_t *self) { - self->has_lock = false; +void common_hal_zephyr_i2c_i2c_unlock(zephyr_i2c_i2c_obj_t *self) { + k_mutex_unlock(&self->mutex); } -uint8_t common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t addr, const uint8_t *data, size_t len) { +uint8_t common_hal_zephyr_i2c_i2c_write(zephyr_i2c_i2c_obj_t *self, uint16_t addr, + const uint8_t *data, size_t len) { + + if (common_hal_zephyr_i2c_i2c_deinited(self)) { + return MP_EIO; + } + + int ret = i2c_write(self->i2c_device, data, len, addr); + if (ret != 0) { + // Map Zephyr error codes to errno + if (ret == -ENOTSUP) { + return MP_EOPNOTSUPP; + } else if (ret == -EIO || ret == -ENXIO) { + return MP_EIO; + } else if (ret == -EBUSY) { + return MP_EBUSY; + } + return MP_EIO; + } + return 0; } -uint8_t common_hal_busio_i2c_read(busio_i2c_obj_t *self, uint16_t addr, uint8_t *data, size_t len) { +uint8_t common_hal_zephyr_i2c_i2c_read(zephyr_i2c_i2c_obj_t *self, uint16_t addr, + uint8_t *data, size_t len) { + + if (common_hal_zephyr_i2c_i2c_deinited(self)) { + return MP_EIO; + } + if (len == 0) { return 0; } + int ret = i2c_read(self->i2c_device, data, len, addr); + if (ret != 0) { + // Map Zephyr error codes to errno + if (ret == -ENOTSUP) { + return MP_EOPNOTSUPP; + } else if (ret == -EIO || ret == -ENXIO) { + return MP_EIO; + } else if (ret == -EBUSY) { + return MP_EBUSY; + } + return MP_EIO; + } + + return 0; } -uint8_t common_hal_busio_i2c_write_read(busio_i2c_obj_t *self, uint16_t addr, +uint8_t common_hal_zephyr_i2c_i2c_write_read(zephyr_i2c_i2c_obj_t *self, uint16_t addr, uint8_t *out_data, size_t out_len, uint8_t *in_data, size_t in_len) { - uint8_t result = _common_hal_busio_i2c_write(self, addr, out_data, out_len, false); - if (result != 0) { - return result; + + if (common_hal_zephyr_i2c_i2c_deinited(self)) { + return MP_EIO; } - return common_hal_busio_i2c_read(self, addr, in_data, in_len); + // Use i2c_write_read for combined transaction with repeated start + int ret = i2c_write_read(self->i2c_device, addr, out_data, out_len, in_data, in_len); + if (ret != 0) { + // Map Zephyr error codes to errno + if (ret == -ENOTSUP) { + return MP_EOPNOTSUPP; + } else if (ret == -EIO || ret == -ENXIO) { + return MP_EIO; + } else if (ret == -EBUSY) { + return MP_EBUSY; + } + return MP_EIO; + } + + return 0; } diff --git a/ports/zephyr-cp/common-hal/zephyr_i2c/I2C.h b/ports/zephyr-cp/common-hal/zephyr_i2c/I2C.h index 22e9251b3f11a..0265511e20894 100644 --- a/ports/zephyr-cp/common-hal/zephyr_i2c/I2C.h +++ b/ports/zephyr-cp/common-hal/zephyr_i2c/I2C.h @@ -1,17 +1,19 @@ // This file is part of the CircuitPython project: https://circuitpython.org // -// SPDX-FileCopyrightText: Copyright (c) 2016 Scott Shawcroft +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries // // SPDX-License-Identifier: MIT #pragma once #include "py/obj.h" +#include typedef struct { mp_obj_base_t base; - // twim_peripheral_t *twim_peripheral; + const struct device *i2c_device; + struct k_mutex mutex; bool has_lock; - uint8_t scl_pin_number; - uint8_t sda_pin_number; -} busio_i2c_obj_t; +} zephyr_i2c_i2c_obj_t; + +mp_obj_t zephyr_i2c_i2c_zephyr_init(zephyr_i2c_i2c_obj_t *self, const struct device *i2c_device); diff --git a/ports/zephyr-cp/common-hal/zephyr_serial/UART.c b/ports/zephyr-cp/common-hal/zephyr_serial/UART.c index fcc05c22f1aad..55e9c424ffed5 100644 --- a/ports/zephyr-cp/common-hal/zephyr_serial/UART.c +++ b/ports/zephyr-cp/common-hal/zephyr_serial/UART.c @@ -74,6 +74,12 @@ void zephyr_serial_uart_construct(zephyr_serial_uart_obj_t *self, const struct d uart_irq_rx_enable(uart_device); } +mp_obj_t zephyr_serial_uart_zephyr_init(zephyr_serial_uart_obj_t *self, const struct device *uart_device) { + self->base.type = &zephyr_serial_uart_type; + zephyr_serial_uart_construct(self, uart_device, 128, NULL); + return MP_OBJ_FROM_PTR(self); +} + bool zephyr_serial_uart_deinited(zephyr_serial_uart_obj_t *self) { return !device_is_ready(self->uart_device); } diff --git a/ports/zephyr-cp/common-hal/zephyr_serial/UART.h b/ports/zephyr-cp/common-hal/zephyr_serial/UART.h index 4e220ee630755..eb3cdd746c7d0 100644 --- a/ports/zephyr-cp/common-hal/zephyr_serial/UART.h +++ b/ports/zephyr-cp/common-hal/zephyr_serial/UART.h @@ -20,3 +20,5 @@ typedef struct { bool rx_paused; // set by irq if no space in rbuf } zephyr_serial_uart_obj_t; + +mp_obj_t zephyr_serial_uart_zephyr_init(zephyr_serial_uart_obj_t *self, const struct device *uart_device); diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index fd1ca7dc43ffc..abb1969470a70 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -51,7 +51,6 @@ "json", "random", "digitalio", - "zephyr_serial", ] MPCONFIG_FLAGS = ["ulab", "nvm", "displayio", "warnings", "alarm", "array", "json"] @@ -254,7 +253,9 @@ async def build_circuitpython(): ) ) - board_autogen_task = tg.create_task(zephyr_dts_to_cp_board(builddir, zephyrbuilddir)) + board_autogen_task = tg.create_task( + zephyr_dts_to_cp_board(portdir, builddir, zephyrbuilddir) + ) board_info = board_autogen_task.result() mpconfigboard_fn = board_tools.find_mpconfigboard(portdir, board) mpconfigboard = { @@ -290,6 +291,12 @@ async def build_circuitpython(): module_reasons["socketpool"] = "Zephyr networking enabled" module_reasons["ssl"] = "Zephyr networking enabled" + for port_module in (portdir / "bindings").iterdir(): + if not board_info.get(port_module.name, False): + continue + enabled_modules.add(port_module.name) + module_reasons[port_module.name] = f"Zephyr board has {port_module.name}" + circuitpython_flags.extend(board_info["cflags"]) supervisor_source = [ "main.c", @@ -301,6 +308,7 @@ async def build_circuitpython(): portdir / "common-hal/microcontroller/Processor.c", portdir / "common-hal/os/__init__.c", "shared/readline/readline.c", + "shared/runtime/buffer_helper.c", "shared/runtime/context_manager_helpers.c", "shared/runtime/pyexec.c", "shared/runtime/interrupt_char.c", diff --git a/ports/zephyr-cp/cptools/zephyr2cp.py b/ports/zephyr-cp/cptools/zephyr2cp.py index 071334161b94c..9d238a03bd810 100644 --- a/ports/zephyr-cp/cptools/zephyr2cp.py +++ b/ports/zephyr-cp/cptools/zephyr2cp.py @@ -7,12 +7,18 @@ from devicetree import dtlib logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) # GPIO flags defined here: include/zephyr/dt-bindings/gpio/gpio.h GPIO_ACTIVE_LOW = 1 << 0 +MINIMUM_RAM_SIZE = 1024 + MANUAL_COMPAT_TO_DRIVER = { "renesas_ra_nv_flash": "flash", + "nordic_nrf_uarte": "serial", + "nordic_nrf_uart": "serial", + "nordic_nrf_twim": "i2c", } # These are controllers, not the flash devices themselves. @@ -22,6 +28,8 @@ "nordic,nrf-spim", ) +DRIVER_CLASSES = {"serial": "UART", "i2c": "I2C", "spi": "SPI"} + CONNECTORS = { "mikro-bus": [ "AN", @@ -104,6 +112,22 @@ "GPIO0", "GPIO1", ], + "raspberrypi,csi-connector": [ + "CSI_D0_N", + "CSI_D0_P", + "CSI_D1_N", + "CSI_D1_P", + "CSI_CK_N", + "CSI_CK_P", + "CSI_D2_N", + "CSI_D2_P", + "CSI_D3_N", + "CSI_D3_P", + "IO0", + "IO1", + "I2C_SCL", + "I2C_SDA", + ], "renesas,ra-gpio-mipi-header": [ "IIC_SDA", "DISP_BLEN", @@ -167,9 +191,11 @@ ], } +EXCEPTIONAL_DRIVERS = ["entropy", "gpio", "led"] + @cpbuild.run_in_thread -def zephyr_dts_to_cp_board(builddir, zephyrbuilddir): # noqa: C901 +def zephyr_dts_to_cp_board(portdir, builddir, zephyrbuilddir): # noqa: C901 board_dir = builddir / "board" # Auto generate board files from device tree. @@ -218,6 +244,9 @@ def zephyr_dts_to_cp_board(builddir, zephyrbuilddir): # noqa: C901 status_led_inverted = False path2chosen = {} chosen2path = {} + + # Store active Zephyr device labels per-driver so that we can make them available via board. + active_zephyr_devices = {} usb_num_endpoint_pairs = 0 for k in edt_pickle.root.nodes["chosen"].props: value = edt_pickle.root.nodes["chosen"].props[k] @@ -238,16 +267,20 @@ def zephyr_dts_to_cp_board(builddir, zephyrbuilddir): # noqa: C901 compatible = [] if "compatible" in node.props: compatible = node.props["compatible"].to_strings() - logger.debug(node.name, status) + logger.debug(f"{node.name}: {status}") + logger.debug(f"compatible: {compatible}") chosen = None if node in path2chosen: chosen = path2chosen[node] - logger.debug(" chosen:", chosen) + logger.debug(f" chosen: {chosen}") + if not compatible and chosen == "zephyr,sram": + # The designated sram region may not have any compatible properties, + # so we assume it is compatible with mmio + compatible.append("mmio") for c in compatible: underscored = c.replace(",", "_").replace("-", "_") driver = COMPAT_TO_DRIVER.get(underscored, None) if "mmio" in c: - logger.debug(" ", c, node.labels, node.props) address, size = node.props["reg"].to_nums() end = address + size if chosen == "zephyr,sram": @@ -270,20 +303,21 @@ def zephyr_dts_to_cp_board(builddir, zephyrbuilddir): # noqa: C901 info = (node.labels[0], start, end, size, node.path) if chosen == "zephyr,sram": rams.insert(0, info) - else: + elif status == "okay" and size > MINIMUM_RAM_SIZE: + logger.debug(f"Adding RAM info: {info}") rams.append(info) if not driver: driver = MANUAL_COMPAT_TO_DRIVER.get(underscored, None) - logger.debug(" ", underscored, driver) - if not driver: + logger.debug(f" {c} -> {underscored} -> {driver}") + if not driver or status != "okay": continue - if driver == "flash" and status == "okay": + if driver == "flash": if not chosen and compatible[0] not in BLOCKED_FLASH_COMPAT: # Skip chosen nodes because they are used by Zephyr. flashes.append(f"DEVICE_DT_GET(DT_NODELABEL({node.labels[0]}))") else: logger.debug(" skipping due to blocked compat") - if driver == "usb/udc" and status == "okay": + elif driver == "usb/udc": board_info["usb_device"] = True props = node.props if "num-bidir-endpoints" not in props: @@ -297,8 +331,18 @@ def zephyr_dts_to_cp_board(builddir, zephyrbuilddir): # noqa: C901 single_direction_endpoints.append(props[eps].to_num() if eps in props else 0) # Count separate in/out pairs as bidirectional. usb_num_endpoint_pairs += min(single_direction_endpoints) - if driver.startswith("wifi") and status == "okay": + elif driver.startswith("wifi"): board_info["wifi"] = True + elif driver in EXCEPTIONAL_DRIVERS: + pass + elif (portdir / f"bindings/zephyr_{driver}").exists(): + board_info[f"zephyr_{driver}"] = True + logger.info(f"Supported driver: {driver}") + if driver not in active_zephyr_devices: + active_zephyr_devices[driver] = [] + active_zephyr_devices[driver].append(node.labels) + else: + logger.warning(f"Unsupported driver: {driver}") if gpio: if "ngpios" in node.props: @@ -391,6 +435,58 @@ def zephyr_dts_to_cp_board(builddir, zephyrbuilddir): # noqa: C901 board_pin_mapping = "\n ".join(board_pin_mapping) mcu_pin_mapping = "\n ".join(mcu_pin_mapping) + zephyr_binding_headers = [] + zephyr_binding_objects = [] + zephyr_binding_labels = [] + for driver, instances in active_zephyr_devices.items(): + driverclass = DRIVER_CLASSES[driver] + zephyr_binding_headers.append(f'#include "bindings/zephyr_{driver}/{driverclass}.h"') + + # Designate a main bus such as board.I2C. + if len(instances) == 1: + instances[0].append(driverclass) + else: + # Check to see if a main bus has already been designated + found_main = False + for labels in instances: + for label in labels: + if label == driverclass: + found_main = True + if not found_main: + for priority_label in ("zephyr_i2c", "arduino_i2c"): + for labels in instances: + if priority_label in labels: + labels.append(driverclass) + found_main = True + break + if found_main: + break + for labels in instances: + instance_name = f"{driver}_{labels[0]}" + c_function_name = f"_{instance_name}" + singleton_ptr = f"{c_function_name}_singleton" + function_object = f"{c_function_name}_obj" + binding_prefix = f"zephyr_{driver}_{driverclass.lower()}" + zephyr_binding_objects.append( + f"""static {binding_prefix}_obj_t {instance_name}_obj; +static mp_obj_t {singleton_ptr} = mp_const_none; +static mp_obj_t {c_function_name}(void) {{ + if ({singleton_ptr} != mp_const_none) {{ + return {singleton_ptr}; + }} + {singleton_ptr} = {binding_prefix}_zephyr_init(&{instance_name}_obj, DEVICE_DT_GET(DT_NODELABEL({labels[0]}))); + return {singleton_ptr}; +}} +static MP_DEFINE_CONST_FUN_OBJ_0({function_object}, {c_function_name});""" + ) + for label in labels: + zephyr_binding_labels.append( + f"{{ MP_ROM_QSTR(MP_QSTR_{label.upper()}), MP_ROM_PTR(&{function_object}) }}," + ) + zephyr_binding_headers = "\n".join(zephyr_binding_headers) + zephyr_binding_objects = "\n".join(zephyr_binding_objects) + zephyr_binding_labels = "\n".join(zephyr_binding_labels) + board_dir.mkdir(exist_ok=True, parents=True) header = board_dir / "mpconfigboard.h" if status_led: @@ -442,6 +538,8 @@ def zephyr_dts_to_cp_board(builddir, zephyrbuilddir): # noqa: C901 #include "py/obj.h" #include "py/mphal.h" +{zephyr_binding_headers} + const struct device* const flashes[] = {{ {", ".join(flashes)} }}; const int circuitpy_flash_device_count = {len(flashes)}; @@ -453,6 +551,8 @@ def zephyr_dts_to_cp_board(builddir, zephyrbuilddir): # noqa: C901 {pin_defs} +{zephyr_binding_objects} + static const mp_rom_map_elem_t mcu_pin_globals_table[] = {{ {mcu_pin_mapping} }}; @@ -463,7 +563,8 @@ def zephyr_dts_to_cp_board(builddir, zephyrbuilddir): # noqa: C901 {board_pin_mapping} -// {{ MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&board_uart_obj) }}, +{zephyr_binding_labels} + }}; MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table); diff --git a/ports/zephyr-cp/prj.conf b/ports/zephyr-cp/prj.conf index bd693bb897ea8..8651b2715cc1a 100644 --- a/ports/zephyr-cp/prj.conf +++ b/ports/zephyr-cp/prj.conf @@ -26,3 +26,5 @@ CONFIG_ASSERT=y CONFIG_LOG_BLOCK_IN_THREAD=y CONFIG_EVENTS=y + +CONFIG_I2C=y diff --git a/ports/zephyr-cp/supervisor/flash.c b/ports/zephyr-cp/supervisor/flash.c index 6b893f89abe55..8daef5fcdaa0d 100644 --- a/ports/zephyr-cp/supervisor/flash.c +++ b/ports/zephyr-cp/supervisor/flash.c @@ -111,6 +111,10 @@ void supervisor_flash_init(void) { const struct device *d = flashes[i]; printk("flash %p %s\n", d, d->name); + if (!device_is_ready(d)) { + printk(" not ready\n"); + continue; + } if (covered_by_areas[i]) { printk(" covered by flash area\n"); continue; From 1ddc9d011b98256fa20c3a7a2c758d78a105c214 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 3 Dec 2025 16:21:56 -0800 Subject: [PATCH 2/3] Fix board autogen --- .../zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml index 7a3ea9267c9e9..2d723cbced681 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml @@ -110,7 +110,7 @@ vectorio = false warnings = false watchdog = false wifi = true # Zephyr board has wifi -zephyr_i2c = false +zephyr_i2c = true # Zephyr board has zephyr_i2c zephyr_kernel = false -zephyr_serial = false +zephyr_serial = true # Zephyr board has zephyr_serial zlib = false From 145d06f9aa951cad09e46e63dc76ff03701c5da9 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 3 Dec 2025 16:22:06 -0800 Subject: [PATCH 3/3] Fix doc build --- ports/zephyr-cp/bindings/zephyr_i2c/I2C.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/zephyr-cp/bindings/zephyr_i2c/I2C.c b/ports/zephyr-cp/bindings/zephyr_i2c/I2C.c index 08ce9e966a669..f2fe029ee0148 100644 --- a/ports/zephyr-cp/bindings/zephyr_i2c/I2C.c +++ b/ports/zephyr-cp/bindings/zephyr_i2c/I2C.c @@ -25,8 +25,8 @@ //| .. class:: I2C() //| //| Cannot be instantiated directly. Instead singletons are created using the -//| `board` aliases that match the device tree labels. `board` may list multiple -//| aliases for a single device. For example, `board.I2C1` and `board.ARDUINO_I2C` +//| ``board`` aliases that match the device tree labels. ``board`` may list multiple +//| aliases for a single device. For example, ``board.I2C1`` and ``board.ARDUINO_I2C`` //| may both refer to the same device. //| """ //|