Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions extmod/modasyncio.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@

#if MICROPY_PY_ASYNCIO

// CIRCUITPY-CHANGE
#if CIRCUITPY && !(defined(__unix__) || defined(__APPLE__))
// CIRCUITPY-CHANGE: the unix port has no supervisor.
#if CIRCUITPY && CIRCUITPY_SUPERVISOR
#include "shared-bindings/supervisor/__init__.h"
#endif

Expand Down Expand Up @@ -76,7 +76,7 @@ static mp_obj_t task_queue_make_new(const mp_obj_type_t *type, size_t n_args, si
#define _TICKS_MAX (_TICKS_PERIOD - 1)
#define _TICKS_HALFPERIOD (_TICKS_PERIOD >> 1)

#if !CIRCUITPY || (defined(__unix__) || defined(__APPLE__))
#if !CIRCUITPY || !CIRCUITPY_SUPERVISOR
static mp_obj_t ticks(void) {
return MP_OBJ_NEW_SMALL_INT(mp_hal_ticks_ms() & _TICKS_MAX);
}
Expand Down
2 changes: 1 addition & 1 deletion ports/zephyr-cp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ target_sources(app PRIVATE zephyr_main.c)

# Add native_sim test support helpers.
if(CONFIG_BOARD_NATIVE_SIM)
target_sources(app PRIVATE native_sim_i2c_emul_control.c)
target_sources(app PRIVATE native_sim_i2c_emul_control.c native_sim_spi_loopback_emul.c)
endif()

if(CONFIG_TRACING_PERFETTO)
Expand Down
9 changes: 9 additions & 0 deletions ports/zephyr-cp/boards/native/native_sim/board.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,13 @@
};


/* An SPI loopback for testing: MOSI wired to MISO. */
&spi0 {
loopback: loopback@0 {
compatible = "circuitpython,spi-loopback";
reg = <0>;
spi-max-frequency = <50000000>;
};
};

#include "../../../app.overlay"
9 changes: 9 additions & 0 deletions ports/zephyr-cp/boards/native/native_sim_lfs/board.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,12 @@


#include "../../../app.overlay"

/* An SPI loopback for testing: MOSI wired to MISO. */
&spi0 {
loopback: loopback@0 {
compatible = "circuitpython,spi-loopback";
reg = <0>;
spi-max-frequency = <50000000>;
};
};
23 changes: 10 additions & 13 deletions ports/zephyr-cp/common-hal/busio/SPI.c
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,15 @@ bool common_hal_busio_spi_read(busio_spi_obj_t *self, uint8_t *data, size_t len,

int ret = spi_transceive_signal(self->spi_device, &self->config[self->active_config], &tx, &rx, &self->signal);

// Wait for the transfer to complete while running background tasks. The driver may read the
// fill buffer until then.
int signaled = 0;
int result = 0;
while (ret == 0 && !signaled && !mp_hal_is_interrupted()) {
RUN_BACKGROUND_TASKS;
k_poll_signal_check(&self->signal, &signaled, &result);
}

if (need_free) {
if (used_port_malloc) {
port_free(tx_data);
Expand All @@ -258,19 +267,7 @@ bool common_hal_busio_spi_read(busio_spi_obj_t *self, uint8_t *data, size_t len,
}
}

if (ret != 0) {
return false;
}

// Wait for the transfer to complete while running background tasks
int signaled = 0;
int result = 0;
while (!signaled && !mp_hal_is_interrupted()) {
RUN_BACKGROUND_TASKS;
k_poll_signal_check(&self->signal, &signaled, &result);
}

return signaled && result == 0;
return ret == 0 && signaled && result == 0;
}

bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, const uint8_t *data_out, uint8_t *data_in, size_t len) {
Expand Down
8 changes: 8 additions & 0 deletions ports/zephyr-cp/dts/bindings/circuitpython,spi-loopback.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-FileCopyrightText: 2026 Vladimir Smitka
# SPDX-License-Identifier: MIT

description: SPI loopback for tests, MOSI wired to MISO

compatible: "circuitpython,spi-loopback"

include: spi-device.yaml
22 changes: 22 additions & 0 deletions ports/zephyr-cp/internal-modules/iobroker/src/iobroker.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <errno.h>
#include <stddef.h>

#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>

Expand Down Expand Up @@ -112,13 +114,28 @@ int iobroker_i2c_allocate(package_pin_t sda, package_pin_t scl,
return -ENOSYS;
}

#if defined(CONFIG_BOARD_NATIVE_SIM) && DT_HAS_COMPAT_STATUS_OKAY(zephyr_spi_emul_controller)
// native_sim has no pin routing: any pins get the emulated SPI controller.
#define NATIVE_SIM_SPI DEVICE_DT_GET_ONE(zephyr_spi_emul_controller)
static bool native_sim_spi_in_use;
#endif

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;
#ifdef NATIVE_SIM_SPI
if (native_sim_spi_in_use) {
return -ENODEV;
}
native_sim_spi_in_use = true;
*dev_out = NATIVE_SIM_SPI;
return 0;
#else
(void)dev_out;
return -ENOSYS;
#endif
}

int iobroker_uart_allocate(package_pin_t tx, package_pin_t rx,
Expand All @@ -138,6 +155,11 @@ int iobroker_pwm_allocate(package_pin_t pin, const struct device **dev_out) {
}

bool iobroker_release(const struct device *dev) {
#ifdef NATIVE_SIM_SPI
if (dev == NATIVE_SIM_SPI) {
native_sim_spi_in_use = false;
}
#endif
(void)dev;
LOG_DBG("release: no routing support on this SoC, nothing to release");
return false;
Expand Down
63 changes: 63 additions & 0 deletions ports/zephyr-cp/native_sim_spi_loopback_emul.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// This file is part of the CircuitPython project: https://circuitpython.org
//
// SPDX-FileCopyrightText: Copyright (c) 2026 Vladimir Smitka
//
// SPDX-License-Identifier: MIT

// SPI loopback for native_sim: every byte sent is read back, and zeros when nothing is sent.

#define DT_DRV_COMPAT circuitpython_spi_loopback

#include <zephyr/device.h>
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/spi_emul.h>

static int loopback_io(const struct emul *target, const struct spi_config *config,
const struct spi_buf_set *tx_bufs, const struct spi_buf_set *rx_bufs) {
ARG_UNUSED(target);
ARG_UNUSED(config);
if (rx_bufs == NULL) {
return 0;
}
// A buffer without data is that many idle or dropped bytes, as in Zephyr's drivers.
size_t tx_i = 0;
size_t tx_pos = 0;
for (size_t i = 0; i < rx_bufs->count; i++) {
uint8_t *rx = rx_bufs->buffers[i].buf;
for (size_t j = 0; j < rx_bufs->buffers[i].len; j++) {
uint8_t value = 0;
while (tx_bufs != NULL && tx_i < tx_bufs->count && tx_pos >= tx_bufs->buffers[tx_i].len) {
tx_i++;
tx_pos = 0;
}
if (tx_bufs != NULL && tx_i < tx_bufs->count) {
const uint8_t *tx = tx_bufs->buffers[tx_i].buf;
if (tx != NULL) {
value = tx[tx_pos];
}
tx_pos++;
}
if (rx != NULL) {
rx[j] = value;
}
}
}
return 0;
}

static int loopback_init(const struct emul *target, const struct device *parent) {
ARG_UNUSED(target);
ARG_UNUSED(parent);
return 0;
}

static const struct spi_emul_api loopback_api = {
.io = loopback_io,
};

#define LOOPBACK_DEFINE(n) \
DEVICE_DT_INST_DEFINE(n, NULL, NULL, NULL, NULL, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, NULL); \
EMUL_DT_INST_DEFINE(n, loopback_init, NULL, NULL, &loopback_api, NULL);

DT_INST_FOREACH_STATUS_OKAY(LOOPBACK_DEFINE)
54 changes: 54 additions & 0 deletions ports/zephyr-cp/tests/test_spi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# SPDX-FileCopyrightText: 2026 Vladimir Smitka
# SPDX-License-Identifier: MIT

"""busio.SPI on native_sim, through the SPI loopback device."""

import pytest

LOOPBACK_CODE = """\
import board

spi = board.SPI()
while not spi.try_lock():
pass
spi.configure(baudrate=1_000_000)
bad = 0
for n in (1, 31, 32, 33, 4096):
out = bytes(i * 7 & 0xFF for i in range(n))
inb = bytearray(n)
spi.write_readinto(out, inb)
if inb != out:
bad += 1
print("write_readinto", n, "differs")
spi.readinto(inb, write_value=0xA5)
if inb != b"\\xa5" * n:
bad += 1
print("readinto 0xA5", n, "differs")
spi.readinto(inb)
if inb != bytes(n):
bad += 1
print("readinto 0", n, "differs")
spi.write(out)
out = bytes(range(64))
inb = bytearray(64)
spi.write_readinto(out, inb, out_start=8, out_end=40, in_start=8, in_end=40)
if inb[8:40] != out[8:40] or any(inb[:8]) or any(inb[40:]):
bad += 1
print("slices differ")
spi.unlock()
spi.deinit()
print("frequency", spi.frequency if False else "ok")
print("bad", bad)
print("done")
"""


@pytest.mark.circuitpy_drive({"code.py": LOOPBACK_CODE})
def test_spi_loopback(circuitpython):
"""Blocking transfers come back through the loopback, including slices and reads."""
circuitpython.wait_until_done()

output = circuitpython.serial.all_output
assert "differs" not in output
assert "bad 0" in output
assert "done" in output
2 changes: 1 addition & 1 deletion ports/zephyr-cp/zephyr-config/west.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ manifest:
path: bootloader/mcuboot
- name: zephyr
url: https://github.com/adafruit/zephyr
revision: a4d81519a1affe11254b049ba565f124fa53671d
revision: 3ab36efea64edda80e45ece016753e3686bb763f
clone-depth: 100
import:
# Skip what no CircuitPython board can use, so west update fetches 37
Expand Down
Loading