stm32-vserprog/spi.c

303 lines
10 KiB
C

#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/spi.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/dma.h>
#include "spi.h"
#include "usbcdc.h"
#define likely(x) __builtin_expect((x), 1)
#define unlikely(x) __builtin_expect((x), 0)
/* DMA channel and requests has fixed mapping, do not change */
#define SPI_DMA_RX_CH DMA_CHANNEL2
#define SPI_DMA_TX_CH DMA_CHANNEL3
static uint8_t dma_rxbuf[USBCDC_PKT_SIZE_DAT];
void spi_disable_pins(void) {
/* Configure GPIOs: SS = PA4, SCK = PA5, MISO = PA6, MOSI = PA7 */
#ifdef STM32F0
gpio_mode_setup(GPIO_BANK_SPI1, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_SPI1_SCK | GPIO_SPI1_MOSI | GPIO_SPI1_MISO | GPIO_SPI1_NSS);
#else
gpio_set_mode(GPIO_BANK_SPI1, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO_SPI1_SCK | GPIO_SPI1_MOSI | GPIO_SPI1_MISO | GPIO_SPI1_NSS);
#endif
}
void spi_enable_pins(void) {
/* Configure GPIOs: SS = PA4, SCK = PA5, MISO = PA6, MOSI = PA7 */
#ifdef STM32F0
gpio_mode_setup(GPIO_BANK_SPI1, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_SPI1_SCK | GPIO_SPI1_MOSI);
gpio_mode_setup(GPIO_BANK_SPI1, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_SPI1_MISO);
gpio_mode_setup(GPIO_BANK_SPI1, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_SPI1_NSS); /* SS is manual */
gpio_set_af(GPIO_BANK_SPI1, GPIO_AF0, GPIO_SPI1_SCK | GPIO_SPI1_MOSI | GPIO_SPI1_MISO);
gpio_set_output_options(GPIO_BANK_SPI1, GPIO_OTYPE_PP, GPIO_OSPEED_HIGH, GPIO_SPI1_SCK | GPIO_SPI1_MOSI | GPIO_SPI1_NSS);
#else
gpio_set_mode(GPIO_BANK_SPI1, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO_SPI1_SCK | GPIO_SPI1_MOSI);
gpio_set_mode(GPIO_BANK_SPI1, GPIO_MODE_INPUT, GPIO_CNF_INPUT_PULL_UPDOWN, GPIO_SPI1_MISO);
gpio_set_mode(GPIO_BANK_SPI1, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO_SPI1_NSS); /* SS is manual */
gpio_set(GPIO_BANK_SPI1, GPIO_SPI1_MISO);
#endif
gpio_set(GPIO_BANK_SPI1, GPIO_SPI1_NSS);
}
uint32_t spi_setup(uint32_t speed_hz) {
uint32_t clkdiv;
uint32_t relspd;
rcc_periph_clock_enable(RCC_SPI1);
#ifdef STM32F0
rcc_periph_clock_enable(RCC_DMA);
#else
rcc_periph_clock_enable(RCC_DMA1);
#endif /* STM32F0 */
/* SPI1 is on APB2 which runs at 72MHz. Assume f = f_PCLK / 2 = 36MHz (whereas datasheet says 18MHz max but reference manual has no such word). */
/* Lowest available */
clkdiv = SPI_CR1_BAUDRATE_FPCLK_DIV_256;
relspd = rcc_apb2_frequency / 256;
if(speed_hz >= rcc_apb2_frequency / 128) {
clkdiv = SPI_CR1_BAUDRATE_FPCLK_DIV_128;
relspd = rcc_apb2_frequency / 128;
}
if(speed_hz >= rcc_apb2_frequency / 64) {
clkdiv = SPI_CR1_BAUDRATE_FPCLK_DIV_64;
relspd = rcc_apb2_frequency / 64;
}
if(speed_hz >= rcc_apb2_frequency / 32) {
clkdiv = SPI_CR1_BAUDRATE_FPCLK_DIV_32;
relspd = rcc_apb2_frequency / 32;
}
if(speed_hz >= rcc_apb2_frequency / 16) {
clkdiv = SPI_CR1_BAUDRATE_FPCLK_DIV_16;
relspd = rcc_apb2_frequency / 16;
}
if(speed_hz >= rcc_apb2_frequency / 8) {
clkdiv = SPI_CR1_BAUDRATE_FPCLK_DIV_8;
relspd = rcc_apb2_frequency / 8;
}
if(speed_hz >= rcc_apb2_frequency / 4) {
clkdiv = SPI_CR1_BAUDRATE_FPCLK_DIV_4;
relspd = rcc_apb2_frequency / 4;
}
if(speed_hz >= rcc_apb2_frequency / 2) {
clkdiv = SPI_CR1_BAUDRATE_FPCLK_DIV_2;
relspd = rcc_apb2_frequency / 2;
}
spi_enable_pins();
/* Reset SPI, SPI_CR1 register cleared, SPI is disabled */
spi_reset(SPI1);
/* Set up SPI in Master mode with:
* Clock baud rate: 1/256 of peripheral clock frequency
* Clock polarity: Idle Low
* Clock phase: Data valid on rising edge (1st edge for idle low)
* Data frame format: 8-bit
* Frame format: MSB First
*/
#ifdef STM32F0
spi_init_master(SPI1, clkdiv, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_MSBFIRST);
spi_set_data_size(SPI1, SPI_CR2_DS_8BIT);
spi_fifo_reception_threshold_8bit(SPI1);
#else
spi_init_master(SPI1, clkdiv, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_1, SPI_CR1_DFF_8BIT, SPI_CR1_MSBFIRST);
#endif /* STM32F0 */
/*
* Set NSS management to software.
*
* NOTE:
* Setting nss high is very important, even if we are controlling the GPIO
* ourselves this bit needs to be at least set to 1, otherwise the spi
* peripheral will not send any data out.
*/
spi_enable_software_slave_management(SPI1);
spi_set_nss_high(SPI1);
/* Misc. */
spi_disable_crc(SPI1);
spi_disable_error_interrupt(SPI1);
spi_disable_rx_buffer_not_empty_interrupt(SPI1);
spi_disable_tx_buffer_empty_interrupt(SPI1);
spi_set_full_duplex_mode(SPI1);
spi_set_unidirectional_mode(SPI1);
/* Enable SPI1 periph. */
spi_enable(SPI1);
/* Report actual clock speed selected */
return relspd;
}
static void spi_dma_write(uint16_t len, char *buf) {
static uint8_t tmp = 0;
/* Reset DMA channels */
dma_channel_reset(DMA1, SPI_DMA_RX_CH);
dma_channel_reset(DMA1, SPI_DMA_TX_CH);
/* Configure TX */
dma_set_peripheral_address(DMA1, SPI_DMA_TX_CH, (uint32_t)&SPI1_DR);
dma_set_memory_address(DMA1, SPI_DMA_TX_CH, (uint32_t)buf);
dma_set_number_of_data(DMA1, SPI_DMA_TX_CH, len);
dma_set_read_from_memory(DMA1, SPI_DMA_TX_CH);
dma_enable_memory_increment_mode(DMA1, SPI_DMA_TX_CH);
dma_set_peripheral_size(DMA1, SPI_DMA_TX_CH, DMA_CCR_PSIZE_8BIT);
dma_set_memory_size(DMA1, SPI_DMA_TX_CH, DMA_CCR_MSIZE_8BIT);
dma_set_priority(DMA1, SPI_DMA_TX_CH, DMA_CCR_PL_HIGH);
/* Configure RX (for collecting garbage from SPI controller) */
dma_set_peripheral_address(DMA1, SPI_DMA_RX_CH, (uint32_t)&SPI1_DR);
dma_set_memory_address(DMA1, SPI_DMA_RX_CH, (uint32_t)(&tmp));
dma_set_number_of_data(DMA1, SPI_DMA_RX_CH, len);
dma_set_read_from_peripheral(DMA1, SPI_DMA_RX_CH);
dma_disable_memory_increment_mode(DMA1, SPI_DMA_RX_CH); /* Do not pollute cache */
dma_set_peripheral_size(DMA1, SPI_DMA_RX_CH, DMA_CCR_PSIZE_8BIT);
dma_set_memory_size(DMA1, SPI_DMA_RX_CH, DMA_CCR_MSIZE_8BIT);
dma_set_priority(DMA1, SPI_DMA_RX_CH, DMA_CCR_PL_VERY_HIGH);
dma_enable_channel(DMA1, SPI_DMA_RX_CH);
dma_enable_channel(DMA1, SPI_DMA_TX_CH);
spi_enable_rx_dma(SPI1);
spi_enable_tx_dma(SPI1);
}
static void spi_dma_read(uint16_t len) {
static uint8_t tmp = 0;
/* Reset DMA channels */
dma_channel_reset(DMA1, SPI_DMA_RX_CH);
dma_channel_reset(DMA1, SPI_DMA_TX_CH);
/* Configure RX */
dma_set_peripheral_address(DMA1, SPI_DMA_RX_CH, (uint32_t)&SPI1_DR);
dma_set_memory_address(DMA1, SPI_DMA_RX_CH, (uint32_t)dma_rxbuf);
dma_set_number_of_data(DMA1, SPI_DMA_RX_CH, len);
dma_set_read_from_peripheral(DMA1, SPI_DMA_RX_CH);
dma_enable_memory_increment_mode(DMA1, SPI_DMA_RX_CH);
dma_set_peripheral_size(DMA1, SPI_DMA_RX_CH, DMA_CCR_PSIZE_8BIT);
dma_set_memory_size(DMA1, SPI_DMA_RX_CH, DMA_CCR_MSIZE_8BIT);
dma_set_priority(DMA1, SPI_DMA_RX_CH, DMA_CCR_PL_VERY_HIGH);
/* Configure TX (for SPI clock) */
dma_set_peripheral_address(DMA1, SPI_DMA_TX_CH, (uint32_t)&SPI1_DR);
dma_set_memory_address(DMA1, SPI_DMA_TX_CH, (uint32_t)(&tmp));
dma_set_number_of_data(DMA1, SPI_DMA_TX_CH, len);
dma_set_read_from_memory(DMA1, SPI_DMA_TX_CH);
dma_disable_memory_increment_mode(DMA1, SPI_DMA_TX_CH); /* Do not pollute cache */
dma_set_peripheral_size(DMA1, SPI_DMA_TX_CH, DMA_CCR_PSIZE_8BIT);
dma_set_memory_size(DMA1, SPI_DMA_TX_CH, DMA_CCR_MSIZE_8BIT);
dma_set_priority(DMA1, SPI_DMA_TX_CH, DMA_CCR_PL_HIGH);
dma_enable_channel(DMA1, SPI_DMA_RX_CH);
dma_enable_channel(DMA1, SPI_DMA_TX_CH);
spi_enable_rx_dma(SPI1);
spi_enable_tx_dma(SPI1);
}
#ifdef GD32F103
/* Old CPU copying code */
static void spi_copy_to_usb(uint32_t len) {
while (len) {
spi_send(SPI1, 0x00);
usbcdc_putc(spi_read(SPI1));
len --;
}
}
static void spi_copy_from_usb(uint32_t len) {
while (len) {
spi_send(SPI1, usbcdc_getc());
spi_read(SPI1);
len --;
}
}
/* FIXME: Currently SPI does not work on GD32 under any clock... */
void spi_bulk_read(uint32_t rlen) {
spi_copy_to_usb(rlen);
}
void spi_bulk_write(uint32_t slen) {
spi_copy_from_usb(slen);
}
#else
void spi_bulk_read(uint32_t rlen) {
while (likely(rlen >= USBCDC_PKT_SIZE_DAT)) {
spi_dma_read(USBCDC_PKT_SIZE_DAT);
rlen -= USBCDC_PKT_SIZE_DAT;
while (unlikely(!dma_get_interrupt_flag(DMA1, SPI_DMA_RX_CH, DMA_TCIF))); /* It is likely, but we want to exit loop with low latency. */
dma_clear_interrupt_flags(DMA1, SPI_DMA_RX_CH, DMA_TCIF);
spi_disable_rx_dma(SPI1);
spi_disable_tx_dma(SPI1);
dma_disable_channel(DMA1, SPI_DMA_RX_CH);
dma_disable_channel(DMA1, SPI_DMA_TX_CH);
usbcdc_write(dma_rxbuf, USBCDC_PKT_SIZE_DAT);
}
/* Leftover only happens when reading individual registers. */
if (unlikely(rlen > 0)) {
spi_dma_read(rlen);
while(unlikely(!dma_get_interrupt_flag(DMA1, SPI_DMA_RX_CH, DMA_TCIF)));
dma_clear_interrupt_flags(DMA1, SPI_DMA_RX_CH, DMA_TCIF);
spi_disable_rx_dma(SPI1);
spi_disable_tx_dma(SPI1);
dma_disable_channel(DMA1, SPI_DMA_RX_CH);
dma_disable_channel(DMA1, SPI_DMA_TX_CH);
usbcdc_write(dma_rxbuf, rlen);
}
}
void spi_bulk_write(uint32_t slen) {
uint8_t urlen;
char *urbuf;
urlen = usbcdc_get_remainder(&urbuf);
/* Due to the characteristics of flashrom and serprog protocol, slen >= urlen. */
if (urlen > 0) {
spi_dma_write(urlen, urbuf);
slen -= urlen;
/* Always check RX flag to avoid leftovers in SPI_DR, which messes data up. */
while (unlikely(!dma_get_interrupt_flag(DMA1, SPI_DMA_RX_CH, DMA_TCIF))); /* It is likely, but we want to exit loop with low latency. */
dma_clear_interrupt_flags(DMA1, SPI_DMA_RX_CH, DMA_TCIF);
spi_disable_rx_dma(SPI1);
spi_disable_tx_dma(SPI1);
dma_disable_channel(DMA1, SPI_DMA_RX_CH);
dma_disable_channel(DMA1, SPI_DMA_TX_CH);
}
/* We have no control over packet size here. */
while (likely(slen > 0)) {
urlen = usbcdc_fetch_packet();
spi_dma_write(urlen, usbcdc_rxbuf);
slen -= urlen;
while (unlikely(!dma_get_interrupt_flag(DMA1, SPI_DMA_RX_CH, DMA_TCIF))); /* It is likely, but we want to exit loop with low latency. */
dma_clear_interrupt_flags(DMA1, SPI_DMA_RX_CH, DMA_TCIF);
spi_disable_rx_dma(SPI1);
spi_disable_tx_dma(SPI1);
dma_disable_channel(DMA1, SPI_DMA_RX_CH);
dma_disable_channel(DMA1, SPI_DMA_TX_CH);
}
/* Mark USB RX buffer as used. */
usbcdc_get_remainder(&urbuf);
}
#endif /* GD32F103 */