//go:build stm32 && !stm32f7x2 && !stm32l5x2

package machine

// Peripheral abstraction layer for SPI on the stm32 family

import (
	"device/stm32"
	"runtime/volatile"
	"unsafe"
)

// SPIConfig is used to store config info for SPI.
type SPIConfig struct {
	Frequency uint32
	SCK       Pin
	SDO       Pin
	SDI       Pin
	LSBFirst  bool
	Mode      uint8
}

// Configure is intended to setup the STM32 SPI1 interface.
func (spi *SPI) Configure(config SPIConfig) error {

	// -- CONFIGURING THE SPI IN MASTER MODE --
	//
	// 1. Select the BR[2:0] bits to define the serial clock baud rate (see
	// 	  SPI_CR1 register).
	// 2. Select the CPOL and CPHA bits to define one of the four relationships
	//    between the data transfer and the serial clock (see Figure 248). This
	//    step is not required when the TI mode is selected.
	// 3. Set the DFF bit to define 8- or 16-bit data frame format
	// 4. Configure the LSBFIRST bit in the SPI_CR1 register to define the frame
	//    format. This step is not required when the TI mode is selected.
	// 5. If the NSS pin is required in input mode, in hardware mode, connect the
	//    NSS pin to a high-level signal during the complete byte transmit
	//    sequence. In NSS software mode, set the SSM and SSI bits in the SPI_CR1
	//    register. If the NSS pin is required in output mode, the SSOE bit only
	//    should be set. This step is not required when the TI mode is selected.
	// 6. Set the FRF bit in SPI_CR2 to select the TI protocol for serial
	//    communications.
	// 7. The MSTR and SPE bits must be set (they remain set only if the NSS pin
	//    is connected to a high-level signal).

	// disable SPI interface before any configuration changes
	spi.Bus.CR1.ClearBits(stm32.SPI_CR1_SPE)

	// enable clock for SPI
	enableAltFuncClock(unsafe.Pointer(spi.Bus))

	// init pins
	if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 {
		config.SCK = SPI0_SCK_PIN
		config.SDO = SPI0_SDO_PIN
		config.SDI = SPI0_SDI_PIN
	}
	spi.configurePins(config)

	// Get SPI baud rate based on the bus speed it's attached to
	var conf uint32 = spi.getBaudRate(config)

	// set bit transfer order
	if config.LSBFirst {
		conf |= stm32.SPI_CR1_LSBFIRST
	}

	// set polarity and phase on the SPI interface
	switch config.Mode {
	case Mode1:
		conf |= stm32.SPI_CR1_CPHA
	case Mode2:
		conf |= stm32.SPI_CR1_CPOL
	case Mode3:
		conf |= stm32.SPI_CR1_CPOL
		conf |= stm32.SPI_CR1_CPHA
	}

	// configure as SPI master
	conf |= stm32.SPI_CR1_MSTR | stm32.SPI_CR1_SSI

	// enable the SPI interface
	conf |= stm32.SPI_CR1_SPE

	// use software CS (GPIO) by default
	conf |= stm32.SPI_CR1_SSM

	// now set the configuration
	spi.Bus.CR1.Set(conf)

	// Series-specific configuration to set 8-bit transfer mode
	spi.config8Bits()

	// enable SPI
	spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE)

	return nil
}

// Transfer writes/reads a single byte using the SPI interface.
func (spi *SPI) Transfer(w byte) (byte, error) {

	// 1. Enable the SPI by setting the SPE bit to 1.
	// 2. Write the first data item to be transmitted into the SPI_DR register
	//    (this clears the TXE flag).
	// 3. Wait until TXE=1 and write the second data item to be transmitted. Then
	//    wait until RXNE=1 and read the SPI_DR to get the first received data
	//    item (this clears the RXNE bit). Repeat this operation for each data
	//    item to be transmitted/received until the n–1 received data.
	// 4. Wait until RXNE=1 and read the last received data.
	// 5. Wait until TXE=1 and then wait until BSY=0 before disabling the SPI.

	// put output word (8-bit) in data register (DR), which is parallel-loaded
	// into shift register, and shifted out on MOSI.  Some series have 16-bit
	// register but writes must be strictly 8-bit to output a byte.  Writing
	// 16-bits indicates a packed transfer (2 bytes).
	(*volatile.Register8)(unsafe.Pointer(&spi.Bus.DR.Reg)).Set(w)

	// wait for SPI bus receive buffer not empty bit (RXNE) to be set.
	// warning: blocks forever until this condition is met.
	for !spi.Bus.SR.HasBits(stm32.SPI_SR_RXNE) {
	}

	// copy input word (8-bit) in data register (DR), which was shifted in on MISO
	// and parallel-loaded into register.
	data := byte(spi.Bus.DR.Get())

	// wait for SPI bus transmit buffer empty bit (TXE) to be set.
	// warning: blocks forever until this condition is met.
	for !spi.Bus.SR.HasBits(stm32.SPI_SR_TXE) {
	}

	// wait for SPI bus busy bit (BSY) to be clear to indicate synchronous
	// transfer complete. this will effectively prevent this Transfer() function
	// from being capable of maintaining high-bandwidth communication throughput,
	// but it will help guarantee stability on the bus.
	for spi.Bus.SR.HasBits(stm32.SPI_SR_BSY) {
	}

	// clear the overrun flag (only in full-duplex mode)
	if !spi.Bus.CR1.HasBits(stm32.SPI_CR1_RXONLY | stm32.SPI_CR1_BIDIMODE | stm32.SPI_CR1_BIDIOE) {
		spi.Bus.SR.Get()
	}

	// Return received data from SPI data register
	return data, nil
}
