pebble/src/fw/drivers/stm32f2/spi.c

697 lines
24 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "spi_definitions.h"
#include "drivers/spi.h"
#include "drivers/spi_dma.h"
#include "drivers/dma.h"
#include "drivers/gpio.h"
#include "drivers/periph_config.h"
#include "system/passert.h"
#include "util/math.h"
#include "util/units.h"
#define STM32F2_COMPATIBLE
#define STM32F4_COMPATIBLE
#define STM32F7_COMPATIBLE
#include <mcu.h>
//! Deduced by looking at the prescalers in stm32f2xx_spi.h
#define SPI_FREQ_LOG_TO_PRESCALER(LG) (((LG) - 1) * 0x8)
//! Bits in CR1 we intend to keep when updating it
#define CR1_CLEAR_MASK ((uint16_t)0x3040)
//! SPI / I2S DMA definitions
typedef enum SpiI2sDma {
SpiI2sDma_ReqTx = 0x0002,
SpiI2sDma_ReqRx = 0x0001
} SpiI2sDma;
//! SPI Master/Slave
typedef enum SpiMode {
SpiMode_Master = 0x0104,
SpiMode_Slave = 0x0000
} SpiMode;
//! SPI Data Size
typedef enum SpiDataSize {
SpiDataSize_16b = 0x0800,
SpiDataSize_8b = 0x0000
} SpiDataSize;
//! SPI Slave Select
typedef enum SpiSlaveSelect {
SpiSlaveSelect_Soft = 0x0200,
SpiSlaveSelect_Hard = 0x0000
} SpiSlaveSelect;
typedef enum {
SpiDisable = 0,
SpiEnable
} SpiFunctionalState;
//
// Private SPI bus functions. No higher level code should
// get access to SPIBus functions or data directly
//
static bool prv_spi_get_flag_status(const SPIBus *bus, SpiI2sFlag flag) {
/* Check the status of the specified SPI flag */
return (bus->spi->SR & (uint16_t)flag) != 0;
}
static bool prv_spi_transmit_is_idle(const SPIBus *bus) {
return prv_spi_get_flag_status(bus, SpiI2sFlag_TXE);
}
static bool prv_spi_receive_is_ready(const SPIBus *bus) {
return prv_spi_get_flag_status(bus, SpiI2sFlag_RXNE);
}
void prv_spi_send_data(const SPIBus *bus, uint16_t Data) {
#if MICRO_FAMILY_STM32F7
// STM32F7 needs to access as 8 bits in order to actually do 8 bits.
// This _does_ work on F4, but QEMU doesn't agree, so let's just do it safely.
*(volatile uint8_t*)&bus->spi->DR = Data;
#else
bus->spi->DR = Data;
#endif
}
uint16_t prv_spi_receive_data(const SPIBus *bus) {
#if MICRO_FAMILY_STM32F7
// STM32F7 needs to access as 8 bits in order to actually do 8 bits.
// This _does_ work on F4, but QEMU doesn't agree, so let's just do it safely.
return *(volatile uint8_t*)&bus->spi->DR;
#else
return bus->spi->DR;
#endif
}
void prv_spi_enable_peripheral_clock(const SPIBus *bus) {
periph_config_enable(bus->spi, bus->state->spi_clock_periph);
}
void prv_spi_disable_peripheral_clock(const SPIBus *bus) {
periph_config_disable(bus->spi, bus->state->spi_clock_periph);
}
static void prv_spi_clear_flags(const SPIBus *bus) {
prv_spi_receive_data(bus);
prv_spi_get_flag_status(bus, (SpiI2sFlag)0);
}
static void prv_spi_dma_cmd(const SPIBus *bus, SpiI2sDma dma_bits, bool enable) {
if (enable) {
bus->spi->CR2 |= (uint16_t)dma_bits;
} else {
bus->spi->CR2 &= (uint16_t)~dma_bits;
}
}
static void prv_spi_cmd(const SPIBus *bus, SpiFunctionalState state) {
if (state != SpiDisable) {
/* Enable the selected SPI peripheral */
bus->spi->CR1 |= SPI_CR1_SPE;
} else {
/* Disable the selected SPI peripheral */
bus->spi->CR1 &= (uint16_t)~((uint16_t)SPI_CR1_SPE);
}
}
void prv_spi_pick_peripheral(const SPIBus *bus) {
RCC_ClocksTypeDef clocks;
RCC_GetClocksFreq(&clocks);
if (bus->spi == SPI1) {
bus->state->spi_clock_periph = RCC_APB2Periph_SPI1;
bus->state->spi_clock_periph_speed = clocks.PCLK2_Frequency;
bus->state->spi_apb = SpiAPB_2;
} else if (bus->spi == SPI2) {
bus->state->spi_clock_periph = RCC_APB1Periph_SPI2;
bus->state->spi_clock_periph_speed = clocks.PCLK1_Frequency;
bus->state->spi_apb = SpiAPB_1;
} else if (bus->spi == SPI3) {
bus->state->spi_clock_periph = RCC_APB1Periph_SPI3;
bus->state->spi_clock_periph_speed = clocks.PCLK1_Frequency;
bus->state->spi_apb = SpiAPB_1;
#ifdef SPI4
} else if (bus->spi == SPI4) {
bus->state->spi_clock_periph = RCC_APB2Periph_SPI4;
bus->state->spi_clock_periph_speed = clocks.PCLK2_Frequency;
bus->state->spi_apb = SpiAPB_2;
#endif
#ifdef SPI5
} else if (bus->spi == SPI5) {
bus->state->spi_clock_periph = RCC_APB2Periph_SPI5;
bus->state->spi_clock_periph_speed = clocks.PCLK2_Frequency;
bus->state->spi_apb = SpiAPB_2;
#endif
#ifdef SPI6
} else if (bus->spi == SPI6) {
bus->state->spi_clock_periph = RCC_APB2Periph_SPI6;
bus->state->spi_clock_periph_speed = clocks.PCLK2_Frequency;
bus->state->spi_apb = SpiAPB_2;
#endif
}
}
static uint16_t prv_spi_find_prescaler(const SPIBus *bus) {
int lg;
if (bus->state->spi_clock_speed_hz > (bus->state->spi_clock_periph_speed / 2)) {
lg = 1; // Underclock to the highest possible frequency
} else {
uint32_t divisor = bus->state->spi_clock_periph_speed / bus->state->spi_clock_speed_hz;
lg = ceil_log_two(divisor);
}
// Prescalers only exists for values in [2 - 256] range
PBL_ASSERTN(lg > 0);
PBL_ASSERTN(lg < 9);
// return prescaler
return (SPI_FREQ_LOG_TO_PRESCALER(lg));
}
void prv_spi_transmit_flush_blocking(const SPIBus *bus) {
while (!prv_spi_transmit_is_idle(bus)) continue;
}
void prv_spi_receive_wait_ready_blocking(const SPIBus *bus) {
while (!prv_spi_receive_is_ready(bus)) continue;
}
static void prv_configure_spi_sclk(const AfConfig *clk_pin, uint16_t spi_sclk_speed) {
gpio_af_init(clk_pin, GPIO_OType_PP, spi_sclk_speed, GPIO_PuPd_NOPULL);
}
static void prv_spi_bus_deinit(const SPIBus *bus, bool is_bidirectional) {
// The pins are no longer in use so reconfigure as analog inputs to save some power
// SCLK
InputConfig sclk = { .gpio = bus->spi_sclk.gpio, .gpio_pin = bus->spi_sclk.gpio_pin };
gpio_analog_init(&sclk);
// MOSI
InputConfig mosi = { .gpio = bus->spi_mosi.gpio, .gpio_pin = bus->spi_mosi.gpio_pin };
gpio_analog_init(&mosi);
// MISO
if (is_bidirectional) {
InputConfig miso = { .gpio = bus->spi_miso.gpio, .gpio_pin = bus->spi_miso.gpio_pin };
gpio_analog_init(&miso);
}
bus->state->initialized = false;
}
void prv_spi_bus_init(const SPIBus *bus, bool is_bidirectional) {
if (bus->state->initialized) {
return;
}
// copy the speed over to the transient state since the slave port can change it
bus->state->spi_clock_speed_hz = bus->spi_clock_speed_hz;
prv_spi_pick_peripheral(bus);
bus->state->initialized = true;
// SCLK
prv_configure_spi_sclk(&bus->spi_sclk, bus->spi_sclk_speed);
// MOSI
gpio_af_init(&bus->spi_mosi, GPIO_OType_PP, bus->spi_sclk_speed, GPIO_PuPd_NOPULL);
// MISO
if (is_bidirectional) {
gpio_af_init(&bus->spi_miso, GPIO_OType_PP, bus->spi_sclk_speed, GPIO_PuPd_NOPULL);
}
}
static void prv_spi_slave_init(const SPISlavePort *slave) {
prv_spi_enable_peripheral_clock(slave->spi_bus);
SPIBus *bus = slave->spi_bus;
// Grab existing configuration
uint16_t tmpreg = bus->spi->CR1;
// Clear BIDIMode, BIDIOE, RxONLY, SSM, SSI, LSBFirst, BR, MSTR, CPOL and CPHA bits
tmpreg &= CR1_CLEAR_MASK;
// get the baudrate prescaler
uint32_t prescaler = prv_spi_find_prescaler(bus);
// Master mode, 8 bit Data Size and Soft Slave select are hardcoded
// Direction, CPOL, CPHA, baudrate prescaler and first-bit come from the device config
tmpreg |= (uint16_t)((uint32_t)slave->spi_direction | SpiMode_Master |
SpiDataSize_8b | slave->spi_cpol |
slave->spi_cpha | SpiSlaveSelect_Soft |
prescaler | slave->spi_first_bit);
// Write result back to CR1
bus->spi->CR1 = tmpreg;
#if MICRO_FAMILY_STM32F7
// On STM32F7 we need to set FRXTH in order to do 8-bit transfers.
// If we don't, the MCU always tries to read 16-bits even though we
// specified that the data is 8-bits.
// Why clear isn't 8-bit is beyond me, but ok.
bus->spi->CR2 |= SPI_CR2_FRXTH;
#endif
// Activate the SPI mode (Reset I2SMOD bit in I2SCFGR register)
bus->spi->I2SCFGR &= (uint16_t)~((uint16_t)SPI_I2SCFGR_I2SMOD);
prv_spi_disable_peripheral_clock(slave->spi_bus);
}
static void prv_spi_slave_deinit(const SPISlavePort *slave) {
spi_ll_slave_acquire(slave);
SPIBus *bus = slave->spi_bus;
if (bus->state->spi_apb == SpiAPB_1) {
// Enable SPIx reset state
RCC_APB1PeriphResetCmd(bus->state->spi_clock_periph, ENABLE);
// Release SPIx from reset state
RCC_APB1PeriphResetCmd(bus->state->spi_clock_periph, DISABLE);
} else if (bus->state->spi_apb == SpiAPB_2) {
// Enable SPIx reset state
RCC_APB2PeriphResetCmd(bus->state->spi_clock_periph, ENABLE);
// Release SPIx from reset state
RCC_APB2PeriphResetCmd(bus->state->spi_clock_periph, DISABLE);
}
spi_ll_slave_release(slave);
}
//
//! High level slave port interface
//! This part of the API can be used for fairly straightforward SPI interactions
//! The assertion and deassertion of the SCS line is automatic
//
static bool prv_is_bidirectional(const SPISlavePort *slave) {
bool is_bidirectional = (slave->spi_direction == SpiDirection_2LinesFullDuplex) ||
(slave->spi_direction == SpiDirection_2LinesRxOnly);
return (is_bidirectional);
}
void spi_slave_port_deinit(const SPISlavePort *slave) {
// don't deinitialize twice
if (!slave->slave_state->initialized) {
return;
}
prv_spi_slave_deinit(slave);
prv_spi_bus_deinit(slave->spi_bus, prv_is_bidirectional(slave));
slave->slave_state->initialized = false;
}
void spi_slave_port_init(const SPISlavePort *slave) {
// don't initialize twice
if (slave->slave_state->initialized) {
return;
}
slave->slave_state->initialized = true;
slave->slave_state->acquired = false;
slave->slave_state->scs_selected = false;
prv_spi_bus_init(slave->spi_bus, prv_is_bidirectional(slave));
// SCS
gpio_output_init(&slave->spi_scs, GPIO_OType_PP, slave->spi_bus->spi_sclk_speed);
gpio_output_set(&slave->spi_scs, false); // SCS not asserted (high)
// Set up an SPI
prv_spi_slave_deinit(slave);
prv_spi_slave_init(slave);
// Set up DMA
if (slave->rx_dma) {
dma_request_init(slave->rx_dma);
}
if (slave->tx_dma) {
dma_request_init(slave->tx_dma);
}
}
static void prv_spi_acquire_helper(const SPISlavePort *slave) {
spi_ll_slave_acquire(slave);
spi_ll_slave_scs_assert(slave);
}
static void prv_spi_release_helper(const SPISlavePort *slave) {
spi_ll_slave_scs_deassert(slave);
spi_ll_slave_release(slave);
}
uint8_t spi_slave_read_write(const SPISlavePort *slave, uint8_t out) {
prv_spi_acquire_helper(slave);
uint8_t ret = spi_ll_slave_read_write(slave, out);
prv_spi_release_helper(slave);
return ret;
}
void spi_slave_write(const SPISlavePort *slave, uint8_t out) {
prv_spi_acquire_helper(slave);
spi_ll_slave_write(slave, out);
prv_spi_release_helper(slave);
}
void spi_slave_burst_read(const SPISlavePort *slave, void *in, size_t len) {
prv_spi_acquire_helper(slave);
spi_ll_slave_burst_read(slave, in, len);
prv_spi_release_helper(slave);
}
void spi_slave_burst_write(const SPISlavePort *slave, const void *out, size_t len) {
prv_spi_acquire_helper(slave);
spi_ll_slave_burst_write(slave, out, len);
prv_spi_release_helper(slave);
}
void spi_slave_burst_read_write(const SPISlavePort *slave, const void *out, void *in, size_t len) {
prv_spi_acquire_helper(slave);
spi_ll_slave_burst_read_write(slave, out, in, len);
prv_spi_release_helper(slave);
}
void spi_slave_burst_read_write_scatter(const SPISlavePort *slave,
const SPIScatterGather *sc_info,
size_t num_sg) {
prv_spi_acquire_helper(slave);
spi_ll_slave_burst_read_write_scatter(slave, sc_info, num_sg);
prv_spi_release_helper(slave);
}
void spi_slave_set_frequency(const SPISlavePort *slave, uint32_t frequency_hz) {
slave->spi_bus->state->spi_clock_speed_hz = frequency_hz;
prv_spi_slave_init(slave);
}
void spi_slave_wait_until_idle_blocking(const SPISlavePort *slave) {
while (prv_spi_get_flag_status(slave->spi_bus, SpiI2sFlag_BSY)) continue;
}
uint32_t spi_get_dma_base_address(const SPISlavePort *slave) {
return (uint32_t)&(slave->spi_bus->spi->DR);
}
//
//! Low level slave port interface
//! This part of the API can be used for slightly more complex SPI operations
//! (such as piecemeal reads or writes). Assertion and deassertion of SCS
//! is up to the caller. Asserts in the code will help to ensure that the
//! API is used correctly.
//
void spi_ll_slave_acquire(const SPISlavePort *slave) {
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired == false);
prv_spi_enable_peripheral_clock(slave->spi_bus);
prv_spi_clear_flags(slave->spi_bus);
slave->slave_state->acquired = true;
spi_ll_slave_spi_enable(slave);
}
void spi_ll_slave_release(const SPISlavePort *slave) {
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
spi_slave_wait_until_idle_blocking(slave);
prv_spi_clear_flags(slave->spi_bus);
spi_ll_slave_spi_disable(slave);
slave->slave_state->acquired = false;
prv_spi_disable_peripheral_clock(slave->spi_bus);
}
void spi_ll_slave_spi_enable(const SPISlavePort *slave) {
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
prv_spi_cmd(slave->spi_bus, SpiEnable);
}
void spi_ll_slave_spi_disable(const SPISlavePort *slave) {
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
prv_spi_cmd(slave->spi_bus, SpiDisable);
}
void spi_ll_slave_scs_assert(const SPISlavePort *slave) {
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
PBL_ASSERTN(slave->slave_state->scs_selected == false);
slave->slave_state->scs_selected = true;
gpio_output_set(&slave->spi_scs, true); // SCS asserted (low)
}
void spi_ll_slave_scs_deassert(const SPISlavePort *slave) {
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
PBL_ASSERTN(slave->slave_state->scs_selected);
slave->slave_state->scs_selected = false;
gpio_output_set(&slave->spi_scs, false); // SCS not asserted (high)
}
uint8_t spi_ll_slave_read_write(const SPISlavePort *slave, uint8_t out) {
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
PBL_ASSERTN(slave->slave_state->scs_selected);
prv_spi_transmit_flush_blocking(slave->spi_bus);
prv_spi_send_data(slave->spi_bus, out);
prv_spi_receive_wait_ready_blocking(slave->spi_bus);
return prv_spi_receive_data(slave->spi_bus);
}
void spi_ll_slave_write(const SPISlavePort *slave, uint8_t out) {
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
PBL_ASSERTN(slave->slave_state->scs_selected);
prv_spi_transmit_flush_blocking(slave->spi_bus);
prv_spi_send_data(slave->spi_bus, out);
}
void spi_ll_slave_burst_read(const SPISlavePort *slave, void *in, size_t len) {
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
PBL_ASSERTN(slave->slave_state->scs_selected);
uint8_t *cptr = in;
while (len--) {
*(cptr++) = spi_ll_slave_read_write(slave, 0); // useless write-data
}
}
void spi_ll_slave_burst_write(const SPISlavePort *slave, const void *out, size_t len) {
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
const uint8_t *cptr = out;
while (len--) {
prv_spi_send_data(slave->spi_bus, *(cptr++));
prv_spi_transmit_flush_blocking(slave->spi_bus);
}
}
void spi_ll_slave_burst_read_write(const SPISlavePort *slave,
const void *out,
void *in,
size_t len) {
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
const uint8_t *outp = out;
uint8_t *inp = in;
for (size_t n = 0; n < len; ++n) {
uint8_t byte_out = outp ? *(outp++) : 0;
uint8_t byte_in = spi_ll_slave_read_write(slave, byte_out);
if (inp) {
*(inp++) = byte_in;
}
}
}
void spi_ll_slave_burst_read_write_scatter(const SPISlavePort *slave,
const SPIScatterGather *sc_info,
size_t num_sg) {
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
for (size_t elem = 0; elem < num_sg; ++elem) {
const SPIScatterGather *sg = &sc_info[elem];
spi_ll_slave_burst_read_write(slave, sg->sg_out, sg->sg_in, sg->sg_len);
}
}
static bool prv_dma_irq_handler(DMARequest *request, void *context) {
const SPISlavePort *slave = context;
PBL_ASSERTN(slave);
bool is_done = false;
switch (slave->slave_state->dma_state) {
case SPISlavePortDMAState_Read:
case SPISlavePortDMAState_Write:
case SPISlavePortDMAState_ReadWriteOneInterrupt:
slave->slave_state->dma_state = SPISlavePortDMAState_Idle;
is_done = true;
break;
case SPISlavePortDMAState_ReadWrite:
slave->slave_state->dma_state = SPISlavePortDMAState_ReadWriteOneInterrupt;
break;
case SPISlavePortDMAState_Idle:
default:
WTF;
}
SPIDMACompleteHandler handler = slave->slave_state->dma_complete_handler;
if (is_done && handler) {
return handler(slave, slave->slave_state->dma_complete_context);
}
return false;
}
void spi_ll_slave_read_dma_start(const SPISlavePort *slave, void *in, size_t len,
SPIDMACompleteHandler handler, void *context) {
PBL_ASSERTN(slave->rx_dma);
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
PBL_ASSERTN(slave->slave_state->dma_state == SPISlavePortDMAState_Idle);
slave->slave_state->dma_state = SPISlavePortDMAState_Read;
slave->slave_state->dma_complete_handler = handler;
slave->slave_state->dma_complete_context = context;
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqRx, true);
dma_request_start_direct(slave->rx_dma, in, (void *)&slave->spi_bus->spi->DR, len,
prv_dma_irq_handler, (void *)slave);
}
void spi_ll_slave_read_dma_stop(const SPISlavePort *slave) {
if (slave->slave_state->dma_state != SPISlavePortDMAState_Read) {
return;
}
dma_request_stop(slave->rx_dma);
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqRx, false);
slave->slave_state->dma_complete_handler = NULL;
slave->slave_state->dma_complete_context = NULL;
}
void spi_ll_slave_write_dma_start(const SPISlavePort *slave, const void *out, size_t len,
SPIDMACompleteHandler handler, void *context) {
PBL_ASSERTN(slave->tx_dma);
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
PBL_ASSERTN(slave->slave_state->dma_state == SPISlavePortDMAState_Idle);
slave->slave_state->dma_state = SPISlavePortDMAState_Write;
slave->slave_state->dma_complete_handler = handler;
slave->slave_state->dma_complete_context = context;
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqTx, true);
dma_request_start_direct(slave->tx_dma, (void *)&slave->spi_bus->spi->DR, out, len,
prv_dma_irq_handler, (void *)slave);
}
void spi_ll_slave_write_dma_stop(const SPISlavePort *slave) {
if (slave->slave_state->dma_state != SPISlavePortDMAState_Write) {
return;
}
dma_request_stop(slave->tx_dma);
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqTx, false);
slave->slave_state->dma_complete_handler = NULL;
slave->slave_state->dma_complete_context = NULL;
}
void spi_ll_slave_read_write_dma_start(const SPISlavePort *slave, const void *out, void *in,
size_t len, SPIDMACompleteHandler handler, void *context) {
PBL_ASSERTN(slave->rx_dma && slave->tx_dma);
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
PBL_ASSERTN(slave->slave_state->dma_state == SPISlavePortDMAState_Idle);
slave->slave_state->dma_complete_handler = handler;
slave->slave_state->dma_complete_context = context;
if (out) {
dma_request_set_memory_increment_disabled(slave->tx_dma, false);
} else {
dma_request_set_memory_increment_disabled(slave->tx_dma, true);
static const uint8_t s_zero = 0;
out = &s_zero;
}
if (in) {
slave->slave_state->dma_state = SPISlavePortDMAState_ReadWrite;
// start the read DMA
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqRx, true);
dma_request_start_direct(slave->rx_dma, in, (void *)&slave->spi_bus->spi->DR, len,
prv_dma_irq_handler, (void *)slave);
} else {
slave->slave_state->dma_state = SPISlavePortDMAState_ReadWriteOneInterrupt;
}
// start the write DMA
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqTx, true);
dma_request_start_direct(slave->tx_dma, (void *)&slave->spi_bus->spi->DR, out, len,
prv_dma_irq_handler, (void *)slave);
}
void spi_ll_slave_read_write_dma_stop(const SPISlavePort *slave) {
if ((slave->slave_state->dma_state != SPISlavePortDMAState_ReadWrite) &&
(slave->slave_state->dma_state != SPISlavePortDMAState_ReadWriteOneInterrupt)) {
return;
}
PBL_ASSERTN(slave->tx_dma && slave->rx_dma);
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
dma_request_stop(slave->rx_dma);
dma_request_stop(slave->tx_dma);
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqRx, false);
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqTx, false);
slave->slave_state->dma_complete_handler = NULL;
slave->slave_state->dma_complete_context = NULL;
}
bool spi_ll_slave_dma_in_progress(const SPISlavePort *slave) {
PBL_ASSERTN(slave->tx_dma || slave->rx_dma);
PBL_ASSERTN(slave->slave_state->initialized);
PBL_ASSERTN(slave->slave_state->acquired);
return (slave->rx_dma && dma_request_in_progress(slave->rx_dma)) ||
(slave->tx_dma && dma_request_in_progress(slave->tx_dma));
}
void spi_ll_slave_set_tx_dma(const SPISlavePort *slave, bool enable) {
PBL_ASSERTN(slave->slave_state->initialized);
spi_ll_slave_acquire(slave);
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqTx, enable);
spi_ll_slave_release(slave);
}
void spi_ll_slave_set_rx_dma(const SPISlavePort *slave, bool enable) {
PBL_ASSERTN(slave->slave_state->initialized);
spi_ll_slave_acquire(slave);
prv_spi_dma_cmd(slave->spi_bus, SpiI2sDma_ReqRx, enable);
spi_ll_slave_release(slave);
}
void spi_ll_slave_drive_clock(const SPISlavePort *slave, bool enable) {
const AfConfig *spi_sclk = &slave->spi_bus->spi_sclk;
if (enable) {
OutputConfig clk_as_gpio = {
.gpio = spi_sclk->gpio,
.gpio_pin = spi_sclk->gpio_pin,
.active_high = true,
};
gpio_output_init(&clk_as_gpio, GPIO_OType_PP, GPIO_Speed_50MHz);
gpio_output_set(&clk_as_gpio, false);
} else {
prv_configure_spi_sclk(spi_sclk, slave->spi_bus->spi_sclk_speed);
}
}
void spi_ll_slave_clear_errors(const SPISlavePort *slave) {
// First, empty the RX FIFO by reading the data. If in TX-only mode, it's possible that
// received data (0x00s) will be left in the RX FIFO.
// NOTE: Obviously, do not call this function with transfer in progress.
while (slave->spi_bus->spi->SR & SPI_SR_RXNE) {
(void)slave->spi_bus->spi->DR;
}
// If the FIFO overflowed, the OVR error will be flagged. Clear the error.
(void)slave->spi_bus->spi->DR;
(void)slave->spi_bus->spi->SR;
}