mirror of https://github.com/google/pebble
697 lines
24 KiB
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;
|
|
}
|