|
| 1 | +// This file is part of the CircuitPython project: https://circuitpython.org |
| 2 | +// |
| 3 | +// SPDX-FileCopyrightText: Copyright (c) 2026 Vladimir Smitka |
| 4 | +// |
| 5 | +// SPDX-License-Identifier: MIT |
| 6 | + |
| 7 | +#include "shared-bindings/async_spi/SPI.h" |
| 8 | + |
| 9 | +#include <string.h> |
| 10 | + |
| 11 | +#include "py/runtime.h" |
| 12 | +#include "supervisor/port.h" |
| 13 | +#include "bindings/zephyr_kernel/__init__.h" |
| 14 | + |
| 15 | +#include <errno.h> |
| 16 | +#include <iobroker/iobroker.h> |
| 17 | + |
| 18 | +// Settings take the other config slot: drivers see a change by the pointer. |
| 19 | +static void apply_config(async_spi_spi_obj_t *self, uint32_t frequency, uint16_t operation) { |
| 20 | + struct spi_config *current = &self->config[self->active_config]; |
| 21 | + if (current->frequency == frequency && current->operation == operation) { |
| 22 | + return; |
| 23 | + } |
| 24 | + self->active_config = 1 - self->active_config; |
| 25 | + self->config[self->active_config].frequency = frequency; |
| 26 | + self->config[self->active_config].operation = operation; |
| 27 | +} |
| 28 | + |
| 29 | +static void transfer_done(const struct device *dev, int result, void *userdata) { |
| 30 | + ARG_UNUSED(dev); |
| 31 | + ARG_UNUSED(result); |
| 32 | + async_spi_spi_obj_t *self = userdata; |
| 33 | + CIRCUITPY_ASYNC_FLAG_SET(self->done); |
| 34 | + // A configure() that waited for this transfer. |
| 35 | + if (self->idle_done != NULL) { |
| 36 | + apply_config(self, self->pending_frequency, self->pending_operation); |
| 37 | + CIRCUITPY_ASYNC_FLAG_SET(self->idle_done); |
| 38 | + self->idle_done = NULL; |
| 39 | + } |
| 40 | + port_wake_main_task_from_isr(); |
| 41 | +} |
| 42 | + |
| 43 | +// Zephyr cannot stop a transfer, so this waits for it; deinit() waits too. Does not allocate. |
| 44 | +static void wait_done(async_spi_spi_obj_t *self) { |
| 45 | + if (self->done == NULL) { |
| 46 | + return; |
| 47 | + } |
| 48 | + while (!CIRCUITPY_ASYNC_FLAG_IS_SET(self->done)) { |
| 49 | + RUN_BACKGROUND_TASKS; |
| 50 | + } |
| 51 | + self->done = NULL; |
| 52 | +} |
| 53 | + |
| 54 | +void common_hal_async_spi_spi_construct(async_spi_spi_obj_t *self, const mcu_pin_obj_t *clock, |
| 55 | + const mcu_pin_obj_t *mosi, const mcu_pin_obj_t *miso) { |
| 56 | + const struct device *dev = NULL; |
| 57 | + int ret = iobroker_spi_allocate(clock->package_pin, |
| 58 | + mosi != NULL ? mosi->package_pin : IOBROKER_NO_PIN, |
| 59 | + miso != NULL ? miso->package_pin : IOBROKER_NO_PIN, &dev); |
| 60 | + if (ret < 0) { |
| 61 | + if (ret == -ENODEV) { |
| 62 | + mp_raise_ValueError(MP_ERROR_TEXT("All SPI peripherals are in use")); |
| 63 | + } |
| 64 | + if (ret == -EBUSY) { |
| 65 | + mp_raise_ValueError(MP_ERROR_TEXT("Internal resource(s) in use")); |
| 66 | + } |
| 67 | + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("Use device tree to define %q devices"), MP_QSTR_SPI); |
| 68 | + } |
| 69 | + |
| 70 | + self->spi_device = dev; |
| 71 | + self->clock = clock; |
| 72 | + self->mosi = mosi; |
| 73 | + self->miso = miso; |
| 74 | + self->active_config = 0; |
| 75 | + for (size_t i = 0; i < 2; i++) { |
| 76 | + self->config[i].frequency = 100000; |
| 77 | + self->config[i].operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_LINES_SINGLE; |
| 78 | + } |
| 79 | + self->done = NULL; |
| 80 | + self->fill = NULL; |
| 81 | + self->idle_done = NULL; |
| 82 | + |
| 83 | + // A routed device is initialized now; a fixed one already was (-EALREADY). |
| 84 | + int init_ret = device_init(dev); |
| 85 | + if (init_ret < 0 && init_ret != -EALREADY) { |
| 86 | + common_hal_async_spi_spi_deinit(self); |
| 87 | + raise_zephyr_error(init_ret); |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +bool common_hal_async_spi_spi_deinited(async_spi_spi_obj_t *self) { |
| 92 | + return self->spi_device == NULL; |
| 93 | +} |
| 94 | + |
| 95 | +void common_hal_async_spi_spi_deinit(async_spi_spi_obj_t *self) { |
| 96 | + if (common_hal_async_spi_spi_deinited(self)) { |
| 97 | + return; |
| 98 | + } |
| 99 | + wait_done(self); |
| 100 | + (void)iobroker_release(self->spi_device); |
| 101 | + self->spi_device = NULL; |
| 102 | + self->clock = NULL; |
| 103 | + self->mosi = NULL; |
| 104 | + self->miso = NULL; |
| 105 | +} |
| 106 | + |
| 107 | +static uint16_t operation_of(uint8_t polarity, uint8_t phase, uint8_t bits) { |
| 108 | + uint16_t operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(bits) | SPI_LINES_SINGLE; |
| 109 | + if (polarity) { |
| 110 | + operation |= SPI_MODE_CPOL; |
| 111 | + } |
| 112 | + if (phase) { |
| 113 | + operation |= SPI_MODE_CPHA; |
| 114 | + } |
| 115 | + return operation; |
| 116 | +} |
| 117 | + |
| 118 | +void common_hal_async_spi_spi_configure_start(async_spi_spi_obj_t *self, uint32_t baudrate, |
| 119 | + uint8_t polarity, uint8_t phase, uint8_t bits, circuitpy_async_flag_t *done) { |
| 120 | + CIRCUITPY_ASYNC_FLAG_INIT(done); |
| 121 | + uint16_t operation = operation_of(polarity, phase, bits); |
| 122 | + if (self->done == NULL || CIRCUITPY_ASYNC_FLAG_IS_SET(self->done)) { |
| 123 | + apply_config(self, baudrate, operation); |
| 124 | + CIRCUITPY_ASYNC_FLAG_SET(done); |
| 125 | + return; |
| 126 | + } |
| 127 | + // A later configure() replaces one still waiting. |
| 128 | + if (self->idle_done != NULL) { |
| 129 | + CIRCUITPY_ASYNC_FLAG_SET(self->idle_done); |
| 130 | + } |
| 131 | + self->pending_frequency = baudrate; |
| 132 | + self->pending_operation = operation; |
| 133 | + self->idle_done = done; |
| 134 | +} |
| 135 | + |
| 136 | +mp_obj_t common_hal_async_spi_spi_configure_end(void *context, circuitpy_async_flag_t *done) { |
| 137 | + return mp_const_none; |
| 138 | +} |
| 139 | + |
| 140 | +void common_hal_async_spi_spi_configure_cancel(void *context, circuitpy_async_flag_t *done) { |
| 141 | + async_spi_spi_obj_t *self = context; |
| 142 | + if (self->idle_done == done) { |
| 143 | + self->idle_done = NULL; |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +uint32_t common_hal_async_spi_spi_get_frequency(async_spi_spi_obj_t *self) { |
| 148 | + return self->config[self->active_config].frequency; |
| 149 | +} |
| 150 | + |
| 151 | +// Start a transfer; done is set from the driver's callback, or at once for an empty one. |
| 152 | +static void start(async_spi_spi_obj_t *self, const uint8_t *data_out, uint8_t *data_in, |
| 153 | + size_t len, circuitpy_async_flag_t *done) { |
| 154 | + wait_done(self); |
| 155 | + CIRCUITPY_ASYNC_FLAG_INIT(done); |
| 156 | + self->done = done; |
| 157 | + if (len == 0) { |
| 158 | + CIRCUITPY_ASYNC_FLAG_SET(done); |
| 159 | + return; |
| 160 | + } |
| 161 | + self->tx_buf.buf = (void *)data_out; |
| 162 | + self->tx_buf.len = len; |
| 163 | + self->tx.buffers = &self->tx_buf; |
| 164 | + self->tx.count = data_out != NULL ? 1 : 0; |
| 165 | + self->rx_buf.buf = data_in; |
| 166 | + self->rx_buf.len = len; |
| 167 | + self->rx.buffers = &self->rx_buf; |
| 168 | + self->rx.count = data_in != NULL ? 1 : 0; |
| 169 | + |
| 170 | + int ret = -ENOTSUP; |
| 171 | + if (DEVICE_API_GET(spi, self->spi_device)->transceive_async != NULL) { |
| 172 | + ret = spi_transceive_cb(self->spi_device, &self->config[self->active_config], |
| 173 | + self->tx.count ? &self->tx : NULL, self->rx.count ? &self->rx : NULL, |
| 174 | + transfer_done, self); |
| 175 | + } |
| 176 | + if (ret < 0) { |
| 177 | + CIRCUITPY_ASYNC_FLAG_SET(done); |
| 178 | + self->done = NULL; |
| 179 | + raise_zephyr_error(ret); |
| 180 | + } |
| 181 | +} |
| 182 | + |
| 183 | +void common_hal_async_spi_spi_write_start(async_spi_spi_obj_t *self, const uint8_t *data, |
| 184 | + size_t len, circuitpy_async_flag_t *done) { |
| 185 | + start(self, data, NULL, len, done); |
| 186 | +} |
| 187 | + |
| 188 | +void common_hal_async_spi_spi_readinto_start(async_spi_spi_obj_t *self, uint8_t *data, |
| 189 | + size_t len, uint8_t write_value, circuitpy_async_flag_t *done) { |
| 190 | + wait_done(self); |
| 191 | + // Without a TX buffer Zephyr sends zeros; another value needs a buffer. |
| 192 | + uint8_t *fill = NULL; |
| 193 | + if (write_value != 0) { |
| 194 | + fill = m_malloc(len); |
| 195 | + memset(fill, write_value, len); |
| 196 | + } |
| 197 | + self->fill = fill; |
| 198 | + start(self, fill, data, len, done); |
| 199 | +} |
| 200 | + |
| 201 | +void common_hal_async_spi_spi_write_readinto_start(async_spi_spi_obj_t *self, |
| 202 | + const uint8_t *data_out, uint8_t *data_in, size_t len, circuitpy_async_flag_t *done) { |
| 203 | + start(self, data_out, data_in, len, done); |
| 204 | +} |
| 205 | + |
| 206 | +mp_obj_t common_hal_async_spi_spi_transfer_end(void *context, circuitpy_async_flag_t *done) { |
| 207 | + async_spi_spi_obj_t *self = context; |
| 208 | + if (self->done == done) { |
| 209 | + wait_done(self); |
| 210 | + } |
| 211 | + return mp_const_none; |
| 212 | +} |
| 213 | + |
| 214 | +void common_hal_async_spi_spi_transfer_cancel(void *context, circuitpy_async_flag_t *done) { |
| 215 | + common_hal_async_spi_spi_transfer_end(context, done); |
| 216 | +} |
0 commit comments