291 lines
9.5 KiB
C
291 lines
9.5 KiB
C
/*
|
|
* This file is part of the MicroPython project, http://micropython.org/
|
|
*
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2021 Scott Shawcroft for Adafruit Industries
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "shared-bindings/busio/SPI.h"
|
|
|
|
#include "shared/runtime/interrupt_char.h"
|
|
#include "py/mperrno.h"
|
|
#include "py/runtime.h"
|
|
|
|
#include "supervisor/board.h"
|
|
#include "common-hal/microcontroller/Pin.h"
|
|
#include "shared-bindings/microcontroller/Pin.h"
|
|
|
|
#include "src/rp2_common/hardware_dma/include/hardware/dma.h"
|
|
#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h"
|
|
|
|
#define NO_INSTANCE 0xff
|
|
|
|
STATIC bool never_reset_spi[2];
|
|
STATIC spi_inst_t *spi[2] = {spi0, spi1};
|
|
|
|
void reset_spi(void) {
|
|
for (size_t i = 0; i < 2; i++) {
|
|
if (never_reset_spi[i]) {
|
|
continue;
|
|
}
|
|
|
|
spi_deinit(spi[i]);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
size_t instance_index = NO_INSTANCE;
|
|
|
|
if (half_duplex) {
|
|
mp_raise_NotImplementedError(translate("Half duplex SPI is not implemented"));
|
|
}
|
|
|
|
if (clock->number % 4 == 2) {
|
|
instance_index = (clock->number / 8) % 2;
|
|
}
|
|
if (mosi != NULL) {
|
|
// Make sure the set MOSI matches the clock settings.
|
|
if (mosi->number % 4 != 3 ||
|
|
(mosi->number / 8) % 2 != instance_index) {
|
|
instance_index = NO_INSTANCE;
|
|
}
|
|
}
|
|
if (miso != NULL) {
|
|
// Make sure the set MOSI matches the clock settings.
|
|
if (miso->number % 4 != 0 ||
|
|
(miso->number / 8) % 2 != instance_index) {
|
|
instance_index = NO_INSTANCE;
|
|
}
|
|
}
|
|
|
|
// TODO: Check to see if we're sharing the SPI with a native APA102.
|
|
|
|
if (instance_index > 1) {
|
|
raise_ValueError_invalid_pins();
|
|
}
|
|
|
|
if (instance_index == 0) {
|
|
self->peripheral = spi0;
|
|
} else if (instance_index == 1) {
|
|
self->peripheral = spi1;
|
|
}
|
|
|
|
if ((spi_get_hw(self->peripheral)->cr1 & SPI_SSPCR1_SSE_BITS) != 0) {
|
|
mp_raise_ValueError(translate("SPI peripheral in use"));
|
|
}
|
|
|
|
self->target_frequency = 250000;
|
|
self->real_frequency = spi_init(self->peripheral, self->target_frequency);
|
|
|
|
gpio_set_function(clock->number, GPIO_FUNC_SPI);
|
|
claim_pin(clock);
|
|
self->clock = clock;
|
|
|
|
self->MOSI = mosi;
|
|
if (mosi != NULL) {
|
|
gpio_set_function(mosi->number, GPIO_FUNC_SPI);
|
|
claim_pin(mosi);
|
|
}
|
|
|
|
self->MISO = miso;
|
|
if (miso != NULL) {
|
|
gpio_set_function(miso->number, GPIO_FUNC_SPI);
|
|
claim_pin(miso);
|
|
}
|
|
}
|
|
|
|
void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) {
|
|
never_reset_spi[spi_get_index(self->peripheral)] = true;
|
|
|
|
common_hal_never_reset_pin(self->clock);
|
|
common_hal_never_reset_pin(self->MOSI);
|
|
common_hal_never_reset_pin(self->MISO);
|
|
}
|
|
|
|
bool common_hal_busio_spi_deinited(busio_spi_obj_t *self) {
|
|
return self->clock == NULL;
|
|
}
|
|
|
|
void common_hal_busio_spi_deinit(busio_spi_obj_t *self) {
|
|
if (common_hal_busio_spi_deinited(self)) {
|
|
return;
|
|
}
|
|
never_reset_spi[spi_get_index(self->peripheral)] = false;
|
|
spi_deinit(self->peripheral);
|
|
|
|
common_hal_reset_pin(self->clock);
|
|
common_hal_reset_pin(self->MOSI);
|
|
common_hal_reset_pin(self->MISO);
|
|
self->clock = NULL;
|
|
}
|
|
|
|
bool common_hal_busio_spi_configure(busio_spi_obj_t *self,
|
|
uint32_t baudrate, uint8_t polarity, uint8_t phase, uint8_t bits) {
|
|
if (baudrate == self->target_frequency &&
|
|
polarity == self->polarity &&
|
|
phase == self->phase &&
|
|
bits == self->bits) {
|
|
return true;
|
|
}
|
|
|
|
spi_set_format(self->peripheral, bits, polarity, phase, SPI_MSB_FIRST);
|
|
|
|
self->polarity = polarity;
|
|
self->phase = phase;
|
|
self->bits = bits;
|
|
self->target_frequency = baudrate;
|
|
self->real_frequency = spi_set_baudrate(self->peripheral, baudrate);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool common_hal_busio_spi_try_lock(busio_spi_obj_t *self) {
|
|
bool grabbed_lock = false;
|
|
if (!self->has_lock) {
|
|
grabbed_lock = true;
|
|
self->has_lock = true;
|
|
}
|
|
return grabbed_lock;
|
|
}
|
|
|
|
bool common_hal_busio_spi_has_lock(busio_spi_obj_t *self) {
|
|
return self->has_lock;
|
|
}
|
|
|
|
void common_hal_busio_spi_unlock(busio_spi_obj_t *self) {
|
|
self->has_lock = false;
|
|
}
|
|
|
|
static bool _transfer(busio_spi_obj_t *self,
|
|
const uint8_t *data_out, size_t out_len,
|
|
uint8_t *data_in, size_t in_len) {
|
|
// Use DMA for large transfers if channels are available
|
|
const size_t dma_min_size_threshold = 32;
|
|
int chan_tx = -1;
|
|
int chan_rx = -1;
|
|
size_t len = MAX(out_len, in_len);
|
|
if (len >= dma_min_size_threshold) {
|
|
// Use two DMA channels to service the two FIFOs
|
|
chan_tx = dma_claim_unused_channel(false);
|
|
chan_rx = dma_claim_unused_channel(false);
|
|
}
|
|
bool use_dma = chan_rx >= 0 && chan_tx >= 0;
|
|
if (use_dma) {
|
|
dma_channel_config c = dma_channel_get_default_config(chan_tx);
|
|
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
|
|
channel_config_set_dreq(&c, spi_get_index(self->peripheral) ? DREQ_SPI1_TX : DREQ_SPI0_TX);
|
|
channel_config_set_read_increment(&c, out_len == len);
|
|
channel_config_set_write_increment(&c, false);
|
|
dma_channel_configure(chan_tx, &c,
|
|
&spi_get_hw(self->peripheral)->dr,
|
|
data_out,
|
|
len,
|
|
false);
|
|
|
|
c = dma_channel_get_default_config(chan_rx);
|
|
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
|
|
channel_config_set_dreq(&c, spi_get_index(self->peripheral) ? DREQ_SPI1_RX : DREQ_SPI0_RX);
|
|
channel_config_set_read_increment(&c, false);
|
|
channel_config_set_write_increment(&c, in_len == len);
|
|
dma_channel_configure(chan_rx, &c,
|
|
data_in,
|
|
&spi_get_hw(self->peripheral)->dr,
|
|
len,
|
|
false);
|
|
|
|
dma_start_channel_mask((1u << chan_rx) | (1u << chan_tx));
|
|
while (dma_channel_is_busy(chan_rx) || dma_channel_is_busy(chan_tx)) {
|
|
// TODO: We should idle here until we get a DMA interrupt or something else.
|
|
RUN_BACKGROUND_TASKS;
|
|
}
|
|
}
|
|
|
|
// If we have claimed only one channel successfully, we should release immediately. This also
|
|
// releases the DMA after use_dma has been done.
|
|
if (chan_rx >= 0) {
|
|
dma_channel_unclaim(chan_rx);
|
|
}
|
|
if (chan_tx >= 0) {
|
|
dma_channel_unclaim(chan_tx);
|
|
}
|
|
|
|
if (!use_dma) {
|
|
// Use software for small transfers, or if couldn't claim two DMA channels
|
|
// Never have more transfers in flight than will fit into the RX FIFO,
|
|
// else FIFO will overflow if this code is heavily interrupted.
|
|
const size_t fifo_depth = 8;
|
|
size_t rx_remaining = len;
|
|
size_t tx_remaining = len;
|
|
|
|
while (rx_remaining || tx_remaining) {
|
|
if (tx_remaining && spi_is_writable(self->peripheral) && rx_remaining - tx_remaining < fifo_depth) {
|
|
spi_get_hw(self->peripheral)->dr = (uint32_t)*data_out;
|
|
// Increment only if the buffer is the transfer length. It's 1 otherwise.
|
|
if (out_len == len) {
|
|
data_out++;
|
|
}
|
|
--tx_remaining;
|
|
}
|
|
if (rx_remaining && spi_is_readable(self->peripheral)) {
|
|
*data_in = (uint8_t)spi_get_hw(self->peripheral)->dr;
|
|
// Increment only if the buffer is the transfer length. It's 1 otherwise.
|
|
if (in_len == len) {
|
|
data_in++;
|
|
}
|
|
--rx_remaining;
|
|
}
|
|
RUN_BACKGROUND_TASKS;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool common_hal_busio_spi_write(busio_spi_obj_t *self,
|
|
const uint8_t *data, size_t len) {
|
|
uint32_t data_in;
|
|
return _transfer(self, data, len, (uint8_t *)&data_in, MIN(len, 4));
|
|
}
|
|
|
|
bool common_hal_busio_spi_read(busio_spi_obj_t *self,
|
|
uint8_t *data, size_t len, uint8_t write_value) {
|
|
uint32_t data_out = write_value << 24 | write_value << 16 | write_value << 8 | write_value;
|
|
return _transfer(self, (const uint8_t *)&data_out, MIN(4, len), data, len);
|
|
}
|
|
|
|
bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, const uint8_t *data_out, uint8_t *data_in, size_t len) {
|
|
return _transfer(self, data_out, len, data_in, len);
|
|
}
|
|
|
|
uint32_t common_hal_busio_spi_get_frequency(busio_spi_obj_t *self) {
|
|
return self->real_frequency;
|
|
}
|
|
|
|
uint8_t common_hal_busio_spi_get_phase(busio_spi_obj_t *self) {
|
|
return self->phase;
|
|
}
|
|
|
|
uint8_t common_hal_busio_spi_get_polarity(busio_spi_obj_t *self) {
|
|
return self->polarity;
|
|
}
|