Skip to content

Commit b6dc686

Browse files
committed
SPI WIP
1 parent 7cfb1cb commit b6dc686

File tree

7 files changed

+692
-41
lines changed

7 files changed

+692
-41
lines changed

ports/zephyr-cp/bindings/zephyr_spi/SPI.c

Lines changed: 401 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#pragma once
8+
9+
#include "py/obj.h"
10+
#include "common-hal/zephyr_spi/SPI.h"
11+
12+
extern const mp_obj_type_t zephyr_spi_spi_type;
13+
14+
// Check if the SPI object has been deinitialized
15+
bool common_hal_zephyr_spi_spi_deinited(zephyr_spi_spi_obj_t *self);
16+
17+
// Deinitialize the SPI bus
18+
void common_hal_zephyr_spi_spi_deinit(zephyr_spi_spi_obj_t *self);
19+
20+
// Locking functions
21+
bool common_hal_zephyr_spi_spi_try_lock(zephyr_spi_spi_obj_t *self);
22+
bool common_hal_zephyr_spi_spi_has_lock(zephyr_spi_spi_obj_t *self);
23+
void common_hal_zephyr_spi_spi_unlock(zephyr_spi_spi_obj_t *self);
24+
25+
// Configuration function
26+
bool common_hal_zephyr_spi_spi_configure(zephyr_spi_spi_obj_t *self, uint32_t baudrate, uint8_t polarity, uint8_t phase, uint8_t bits);
27+
28+
// Data transfer functions
29+
bool common_hal_zephyr_spi_spi_write(zephyr_spi_spi_obj_t *self, const uint8_t *data, size_t len);
30+
bool common_hal_zephyr_spi_spi_read(zephyr_spi_spi_obj_t *self, uint8_t *data, size_t len, uint8_t write_value);
31+
bool common_hal_zephyr_spi_spi_transfer(zephyr_spi_spi_obj_t *self, const uint8_t *data_out, uint8_t *data_in, size_t len);
32+
33+
// Get current frequency
34+
uint32_t common_hal_zephyr_spi_spi_get_frequency(zephyr_spi_spi_obj_t *self);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// This file is part of the CircuitPython project: https://circuitpython.org
2+
//
3+
// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries
4+
//
5+
// SPDX-License-Identifier: MIT
6+
7+
#include "py/obj.h"
8+
#include "py/runtime.h"
9+
#include "bindings/zephyr_spi/SPI.h"
10+
11+
//| """Zephyr SPI driver for fixed SPI busses.
12+
//|
13+
//| This module provides access to SPI busses using Zephyr device labels."""
14+
15+
static const mp_rom_map_elem_t zephyr_spi_module_globals_table[] = {
16+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_zephyr_spi) },
17+
{ MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&zephyr_spi_spi_type) },
18+
};
19+
20+
static MP_DEFINE_CONST_DICT(zephyr_spi_module_globals, zephyr_spi_module_globals_table);
21+
22+
const mp_obj_module_t zephyr_spi_module = {
23+
.base = { &mp_type_module },
24+
.globals = (mp_obj_dict_t *)&zephyr_spi_module_globals,
25+
};
26+
27+
MP_REGISTER_MODULE(MP_QSTR_zephyr_spi, zephyr_spi_module);
Lines changed: 217 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,257 @@
11
// This file is part of the CircuitPython project: https://circuitpython.org
22
//
3-
// SPDX-FileCopyrightText: Copyright (c) 2019 Dan Halbert for Adafruit Industries
4-
// SPDX-FileCopyrightText: Copyright (c) 2018 Artur Pacholec
3+
// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries
54
//
65
// SPDX-License-Identifier: MIT
76

8-
#include <string.h>
9-
10-
#include "shared-bindings/busio/SPI.h"
7+
#include "bindings/zephyr_spi/SPI.h"
118
#include "py/mperrno.h"
129
#include "py/runtime.h"
10+
#include "py/gc.h"
11+
#include "shared/runtime/interrupt_char.h"
12+
#include "supervisor/port.h"
1313

14-
void spi_reset(void) {
15-
}
14+
#include <zephyr/drivers/spi.h>
15+
#include <zephyr/device.h>
16+
#include <zephyr/kernel.h>
1617

17-
void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) {
18-
}
18+
mp_obj_t zephyr_spi_spi_zephyr_init(zephyr_spi_spi_obj_t *self, const struct device *spi_device) {
19+
self->base.type = &zephyr_spi_spi_type;
20+
self->spi_device = spi_device;
21+
k_mutex_init(&self->mutex);
22+
self->has_lock = false;
23+
self->active_config = 0;
24+
25+
k_poll_signal_init(&self->signal);
1926

20-
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) {
27+
// Default configuration for both config slots
28+
self->config[0].frequency = 100000;
29+
self->config[0].operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_LINES_SINGLE;
30+
self->config[1].frequency = 100000;
31+
self->config[1].operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_LINES_SINGLE;
2132

33+
return MP_OBJ_FROM_PTR(self);
2234
}
2335

24-
bool common_hal_busio_spi_deinited(busio_spi_obj_t *self) {
36+
bool common_hal_zephyr_spi_spi_deinited(zephyr_spi_spi_obj_t *self) {
37+
// Always leave it active
38+
return false;
2539
}
2640

27-
void common_hal_busio_spi_deinit(busio_spi_obj_t *self) {
28-
if (common_hal_busio_spi_deinited(self)) {
41+
void common_hal_zephyr_spi_spi_deinit(zephyr_spi_spi_obj_t *self) {
42+
if (common_hal_zephyr_spi_spi_deinited(self)) {
2943
return;
3044
}
45+
// Always leave it active
3146
}
3247

33-
bool common_hal_busio_spi_configure(busio_spi_obj_t *self, uint32_t baudrate, uint8_t polarity, uint8_t phase, uint8_t bits) {
34-
return true;
35-
}
36-
37-
bool common_hal_busio_spi_try_lock(busio_spi_obj_t *self) {
38-
if (common_hal_busio_spi_deinited(self)) {
48+
bool common_hal_zephyr_spi_spi_try_lock(zephyr_spi_spi_obj_t *self) {
49+
if (common_hal_zephyr_spi_spi_deinited(self)) {
3950
return false;
4051
}
41-
bool grabbed_lock = false;
42-
return grabbed_lock;
52+
53+
self->has_lock = k_mutex_lock(&self->mutex, K_NO_WAIT) == 0;
54+
return self->has_lock;
4355
}
4456

45-
bool common_hal_busio_spi_has_lock(busio_spi_obj_t *self) {
57+
bool common_hal_zephyr_spi_spi_has_lock(zephyr_spi_spi_obj_t *self) {
4658
return self->has_lock;
4759
}
4860

49-
void common_hal_busio_spi_unlock(busio_spi_obj_t *self) {
61+
void common_hal_zephyr_spi_spi_unlock(zephyr_spi_spi_obj_t *self) {
5062
self->has_lock = false;
63+
k_mutex_unlock(&self->mutex);
5164
}
5265

53-
bool common_hal_busio_spi_write(busio_spi_obj_t *self, const uint8_t *data, size_t len) {
54-
return true;
55-
}
66+
bool common_hal_zephyr_spi_spi_configure(zephyr_spi_spi_obj_t *self, uint32_t baudrate, uint8_t polarity, uint8_t phase, uint8_t bits) {
67+
if (common_hal_zephyr_spi_spi_deinited(self)) {
68+
return false;
69+
}
70+
71+
// Set operation mode based on polarity and phase
72+
uint16_t operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(bits) | SPI_LINES_SINGLE;
73+
74+
if (polarity) {
75+
operation |= SPI_MODE_CPOL;
76+
}
77+
if (phase) {
78+
operation |= SPI_MODE_CPHA;
79+
}
80+
81+
// Check if settings have changed. We must switch to the other config slot if they have because
82+
// Zephyr drivers are allowed to use the pointer value to know if it has changed.
83+
struct spi_config *current_config = &self->config[self->active_config];
84+
if (current_config->frequency != baudrate || current_config->operation != operation) {
85+
// Settings changed, switch to the other config slot
86+
self->active_config = 1 - self->active_config;
87+
88+
// Update the new active configuration
89+
self->config[self->active_config].frequency = baudrate;
90+
self->config[self->active_config].operation = operation;
91+
}
5692

57-
bool common_hal_busio_spi_read(busio_spi_obj_t *self, uint8_t *data, size_t len, uint8_t write_value) {
5893
return true;
5994
}
6095

61-
bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, const uint8_t *data_out, uint8_t *data_in, size_t len) {
62-
return true;
96+
bool common_hal_zephyr_spi_spi_write(zephyr_spi_spi_obj_t *self, const uint8_t *data, size_t len) {
97+
if (common_hal_zephyr_spi_spi_deinited(self)) {
98+
return false;
99+
}
100+
101+
if (len == 0) {
102+
return true;
103+
}
104+
105+
const struct spi_buf tx_buf = {
106+
.buf = (void *)data,
107+
.len = len
108+
};
109+
const struct spi_buf_set tx = {
110+
.buffers = &tx_buf,
111+
.count = 1
112+
};
113+
114+
// Initialize the signal for async operation
115+
k_poll_signal_reset(&self->signal);
116+
117+
int ret = spi_transceive_signal(self->spi_device, &self->config[self->active_config], &tx, NULL, &self->signal);
118+
if (ret != 0) {
119+
return false;
120+
}
121+
122+
// Wait for the transfer to complete while running background tasks
123+
int signaled = 0;
124+
int result = 0;
125+
while (!signaled && !mp_hal_is_interrupted()) {
126+
RUN_BACKGROUND_TASKS;
127+
k_poll_signal_check(&self->signal, &signaled, &result);
128+
}
129+
130+
return signaled && result == 0;
63131
}
64132

65-
uint32_t common_hal_busio_spi_get_frequency(busio_spi_obj_t *self) {
133+
bool common_hal_zephyr_spi_spi_read(zephyr_spi_spi_obj_t *self, uint8_t *data, size_t len, uint8_t write_value) {
134+
if (common_hal_zephyr_spi_spi_deinited(self)) {
135+
return false;
136+
}
137+
138+
if (len == 0) {
139+
return true;
140+
}
141+
142+
// For read, we need to write dummy bytes
143+
// We'll allocate a temporary buffer if write_value is not 0
144+
uint8_t *tx_data = NULL;
145+
bool need_free = false;
146+
bool used_port_malloc = false;
147+
148+
if (write_value != 0) {
149+
// Use port_malloc if GC isn't active, otherwise use m_malloc
150+
if (gc_alloc_possible()) {
151+
tx_data = m_malloc(len);
152+
} else {
153+
tx_data = port_malloc(len, false);
154+
used_port_malloc = true;
155+
}
156+
if (tx_data == NULL) {
157+
return false;
158+
}
159+
memset(tx_data, write_value, len);
160+
need_free = true;
161+
}
162+
163+
const struct spi_buf tx_buf = {
164+
.buf = tx_data,
165+
.len = tx_data ? len : 0
166+
};
167+
const struct spi_buf_set tx = {
168+
.buffers = &tx_buf,
169+
.count = tx_data ? 1 : 0
170+
};
171+
172+
const struct spi_buf rx_buf = {
173+
.buf = data,
174+
.len = len
175+
};
176+
const struct spi_buf_set rx = {
177+
.buffers = &rx_buf,
178+
.count = 1
179+
};
180+
181+
// Initialize the signal for async operation
182+
k_poll_signal_reset(&self->signal);
183+
184+
int ret = spi_transceive_signal(self->spi_device, &self->config[self->active_config], &tx, &rx, &self->signal);
185+
186+
if (need_free) {
187+
if (used_port_malloc) {
188+
port_free(tx_data);
189+
} else {
190+
m_free(tx_data);
191+
}
192+
}
193+
194+
if (ret != 0) {
195+
return false;
196+
}
197+
198+
// Wait for the transfer to complete while running background tasks
199+
int signaled = 0;
200+
int result = 0;
201+
while (!signaled && !mp_hal_is_interrupted()) {
202+
RUN_BACKGROUND_TASKS;
203+
k_poll_signal_check(&self->signal, &signaled, &result);
204+
}
205+
206+
return signaled && result == 0;
66207
}
67208

68-
uint8_t common_hal_busio_spi_get_phase(busio_spi_obj_t *self) {
69-
return 0;
209+
bool common_hal_zephyr_spi_spi_transfer(zephyr_spi_spi_obj_t *self, const uint8_t *data_out, uint8_t *data_in, size_t len) {
210+
if (common_hal_zephyr_spi_spi_deinited(self)) {
211+
return false;
212+
}
213+
214+
if (len == 0) {
215+
return true;
216+
}
217+
218+
const struct spi_buf tx_buf = {
219+
.buf = (void *)data_out,
220+
.len = len
221+
};
222+
const struct spi_buf_set tx = {
223+
.buffers = &tx_buf,
224+
.count = 1
225+
};
226+
227+
const struct spi_buf rx_buf = {
228+
.buf = data_in,
229+
.len = len
230+
};
231+
const struct spi_buf_set rx = {
232+
.buffers = &rx_buf,
233+
.count = 1
234+
};
235+
236+
// Initialize the signal for async operation
237+
k_poll_signal_reset(&self->signal);
238+
239+
int ret = spi_transceive_signal(self->spi_device, &self->config[self->active_config], &tx, &rx, &self->signal);
240+
if (ret != 0) {
241+
return false;
242+
}
243+
244+
// Wait for the transfer to complete while running background tasks
245+
int signaled = 0;
246+
int result = 0;
247+
while (!signaled && !mp_hal_is_interrupted()) {
248+
RUN_BACKGROUND_TASKS;
249+
k_poll_signal_check(&self->signal, &signaled, &result);
250+
}
251+
252+
return signaled && result == 0;
70253
}
71254

72-
uint8_t common_hal_busio_spi_get_polarity(busio_spi_obj_t *self) {
73-
return 0;
255+
uint32_t common_hal_zephyr_spi_spi_get_frequency(zephyr_spi_spi_obj_t *self) {
256+
return self->config[self->active_config].frequency;
74257
}
Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
// This file is part of the CircuitPython project: https://circuitpython.org
22
//
3-
// SPDX-FileCopyrightText: Copyright (c) 2016 Scott Shawcroft
3+
// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries
44
//
55
// SPDX-License-Identifier: MIT
66

77
#pragma once
88

99
#include "py/obj.h"
10+
#include <zephyr/kernel.h>
11+
#include <zephyr/drivers/spi.h>
1012

1113
typedef struct {
1214
mp_obj_base_t base;
13-
// const spim_peripheral_t *spim_peripheral;
15+
const struct device *spi_device;
16+
struct k_mutex mutex;
1417
bool has_lock;
15-
uint8_t clock_pin_number;
16-
uint8_t MOSI_pin_number;
17-
uint8_t MISO_pin_number;
18-
} busio_spi_obj_t;
18+
struct spi_config config[2]; // Two configs for pointer comparison by driver
19+
uint8_t active_config; // Index of currently active config (0 or 1)
20+
struct k_poll_signal signal;
21+
} zephyr_spi_spi_obj_t;
1922

20-
void spi_reset(void);
23+
mp_obj_t zephyr_spi_spi_zephyr_init(zephyr_spi_spi_obj_t *self, const struct device *spi_device);

ports/zephyr-cp/cptools/zephyr2cp.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"nordic_nrf_uarte": "serial",
2020
"nordic_nrf_uart": "serial",
2121
"nordic_nrf_twim": "i2c",
22+
"nordic_nrf_spim": "spi",
2223
}
2324

2425
# These are controllers, not the flash devices themselves.

ports/zephyr-cp/prj.conf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@ CONFIG_LOG_BLOCK_IN_THREAD=y
2828
CONFIG_EVENTS=y
2929

3030
CONFIG_I2C=y
31+
CONFIG_SPI=y
32+
CONFIG_SPI_ASYNC=y

0 commit comments

Comments
 (0)