#include "spi_dma.h"

#include "ch32v003fun.h"
#include "debug.h"

#define RX_Channel DMA1_Channel2
#define TX_Channel DMA1_Channel3

static const uint8_t tx_dummy_byte = 0xFF;
static uint8_t rx_dummy;

void spi_select(void) {
  GPIOA->BCR = (1 << 4);  // Set PA4 (CS) low
}

void spi_unselect(void) {
  GPIOA->BSHR = (1 << 4);  // Set PA4 (CS) high
}

// SPI DMA
void spidma_read_buffer(uint8_t* buf, uint16_t len) {
  // tx
  TX_Channel->MADDR = (uint32_t)&tx_dummy_byte;
  TX_Channel->CNTR = len;
  TX_Channel->CFGR &= ~DMA_MemoryInc_Enable;
  // rx
  RX_Channel->MADDR = (uint32_t)buf;
  RX_Channel->CNTR = len;
  RX_Channel->CFGR |= DMA_MemoryInc_Enable;

  // enable
  RX_Channel->CFGR |= DMA_CFGR1_EN;
  TX_Channel->CFGR |= DMA_CFGR1_EN;

  while (!(DMA1->INTFR & DMA1_FLAG_TC2) && !(DMA1->INTFR & DMA1_FLAG_TC3));

  /**
   * In transmission mode, when the DMA has written all the data to be
   * transmitted (flag TCIF is set in the DMA_ISR register), the BSY flag can be
   * monitored to ensure that the SPI communication is complete. This is
   * required to avoid corrupting the last transmission before disabling the SPI
   * or entering the Stop mode. The software must first wait until TXE=1 and
   * then until BSY=0.
   */
  while (!(SPI1->STATR & SPI_STATR_TXE));
  while (SPI1->STATR & SPI_I2S_FLAG_BSY);

  // clear intfr
  DMA1->INTFCR |= DMA1_FLAG_TC2 | DMA1_FLAG_TC3;

  RX_Channel->CFGR &= ~DMA_CFGR1_EN;
  TX_Channel->CFGR &= ~DMA_CFGR1_EN;
}

void spidma_write_buffer(uint8_t* buf, uint16_t len) {
  // tx
  TX_Channel->MADDR = (uint32_t)buf;
  TX_Channel->CNTR = len;
  TX_Channel->CFGR |= DMA_MemoryInc_Enable;
  // rx
  RX_Channel->MADDR = (uint32_t)&rx_dummy;
  RX_Channel->CNTR = len;
  RX_Channel->CFGR &= ~DMA_MemoryInc_Enable;

  // enable
  TX_Channel->CFGR |= DMA_CFGR1_EN;
  RX_Channel->CFGR |= DMA_CFGR1_EN;

  while (!(DMA1->INTFR & DMA1_FLAG_TC2) && !(DMA1->INTFR & DMA1_FLAG_TC3));

  // wait for SPI to complete: TXE = 1, then BSY = 0
  while (!(SPI1->STATR & SPI_STATR_TXE));
  while (SPI1->STATR & SPI_I2S_FLAG_BSY);

  // clear intfr
  DMA1->INTFCR |= DMA1_FLAG_TC2 | DMA1_FLAG_TC3;

  RX_Channel->CFGR &= ~DMA_CFGR1_EN;
  TX_Channel->CFGR &= ~DMA_CFGR1_EN;
}

void init_spidma(void) {
  // Enable clock for GPIOA and SPI1
  RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1;
  // Enable clock for DMA1
  RCC->AHBPCENR |= RCC_AHBPeriph_DMA1;

  // SPI1 Pin Configuration
  // CS on PA4, 10MHz Output, push-pull
  GPIOA->CFGLR &= ~(0xf << (4 * 4));
  GPIOA->CFGLR |= (GPIO_Speed_10MHz | GPIO_CNF_OUT_PP) << (4 * 4);
  // SCK on PA5, 10MHz Output, alt func, push-pull
  GPIOA->CFGLR &= ~(0xf << (4 * 5));
  GPIOA->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 5);
  // MISO on PA6, 10MHz input, floating
  GPIOA->CFGLR &= ~(0xf << (4 * 6));
  GPIOA->CFGLR |= (GPIO_CNF_IN_FLOATING) << (4 * 6);
  // MOSI on PA7, 10MHz Output, alt func, push-pull
  GPIOA->CFGLR &= ~(0xf << (4 * 7));
  GPIOA->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF) << (4 * 7);

  // Reset and Configure SPI1
  SPI1->CTLR1 = 0;  // Clear CTLR1 initially

  SPI1->CTLR1 = SPI_Mode_Master |                  // Master mode
                SPI_Direction_2Lines_FullDuplex |  // Full duplex
                SPI_DataSize_8b |                  // 8-bit data frame format
                SPI_CPHA_1Edge |                   // Clock polarity
                SPI_CPOL_Low |                     // Clock phase
                SPI_BaudRatePrescaler_64 |         // Baud rate prescaler
                SPI_NSS_Soft;                      // Software NSS management

  // Enable TX and RX DMA
  SPI1->CTLR2 = SPI_CTLR2_TXDMAEN | SPI_CTLR2_RXDMAEN;

  SPI1->I2SCFGR &= SPI_Mode_Select;  // Disable I2S mode
  SPI1->CTLR1 |= SPI_CTLR1_SPE;      // Enable SPI

  // DMA setup
  TX_Channel->PADDR = (uint32_t)&SPI1->DATAR;  // TX Channel
  TX_Channel->CFGR = DMA_M2M_Disable | DMA_Priority_VeryHigh |
                     DMA_MemoryDataSize_Byte | DMA_PeripheralDataSize_Byte |
                     DMA_MemoryInc_Enable | DMA_PeripheralInc_Disable |
                     DMA_Mode_Normal | DMA_DIR_PeripheralDST;

  RX_Channel->PADDR = (uint32_t)&SPI1->DATAR;  // RX Channel
  RX_Channel->CFGR = DMA_M2M_Disable | DMA_Priority_VeryHigh |
                     DMA_MemoryDataSize_Byte | DMA_PeripheralDataSize_Byte |
                     DMA_MemoryInc_Enable | DMA_PeripheralInc_Disable |
                     DMA_Mode_Normal | DMA_DIR_PeripheralSRC;
}
