//go:build stm32

package machine

// Peripheral abstraction layer for UARTs on the stm32 family (except stm32g0).

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

// UART representation
type UART struct {
	Buffer            *RingBuffer
	Bus               *stm32.USART_Type
	Interrupt         interrupt.Interrupt
	TxAltFuncSelector uint8
	RxAltFuncSelector uint8

	// Registers specific to the chip
	rxReg       *volatile.Register32
	txReg       *volatile.Register32
	statusReg   *volatile.Register32
	txEmptyFlag uint32
	// errClearReg points to the ICR register on newer STM32 USART peripherals
	// (L0, L4, L5, G0, F7, U5, WL, etc.) for clearing error flags. Nil for
	// older peripherals (F1, F4) where errors are cleared by reading SR+DR.
	errClearReg *volatile.Register32
}

// Configure the UART.
func (uart *UART) Configure(config UARTConfig) {
	// Default baud rate to 115200.
	if config.BaudRate == 0 {
		config.BaudRate = 115200
	}

	// Set the GPIO pins to defaults if they're not set
	if config.TX == 0 && config.RX == 0 {
		config.TX = UART_TX_PIN
		config.RX = UART_RX_PIN
	}

	// STM32 families have different, but compatible, registers for
	// basic UART functions.  For each family populate the registers
	// into `uart`.
	uart.setRegisters()

	// Enable USART clock
	enableAltFuncClock(unsafe.Pointer(uart.Bus))

	uart.configurePins(config)

	// Set baud rate
	uart.SetBaudRate(config.BaudRate)

	// Configure RX/TX inversion
	uart.setInversion(config)

	// Enable USART port, tx, rx and rx interrupts
	uart.Bus.CR1.Set(stm32.USART_CR1_TE | stm32.USART_CR1_RE | stm32.USART_CR1_RXNEIE | stm32.USART_CR1_UE)

	// Enable RX IRQ
	uart.Interrupt.SetPriority(0xc0)
	uart.Interrupt.Enable()
}

// handleInterrupt should be called from the appropriate interrupt handler for
// this UART instance.
func (uart *UART) handleInterrupt(interrupt.Interrupt) {
	s := uart.statusReg.Get()

	// Only read data when RXNE/RXFNE (bit 5) is set. On all STM32 families,
	// RXNEIE enables both the RX-data-ready and overrun-error (ORE) interrupts.
	// Without this check, an ORE-only interrupt reads garbage from RDR.
	if s&0x20 != 0 { // RXNE / RXFNE
		uart.Receive(byte((uart.rxReg.Get() & 0xFF)))
	}

	// Clear error flags (ORE=bit3, NE=bit2, FE=bit1, PE=bit0) to prevent
	// an interrupt storm and ensure the USART can continue receiving.
	if s&0xF != 0 {
		if uart.errClearReg != nil {
			// Newer USART peripherals (L0, L4, L5, G0, F7, U5, WL, etc.):
			// clear all error flags via ICR (ORECF|NECF|FECF|PECF = bits 3:0).
			uart.errClearReg.Set(s & 0xF)
		} else if s&0x20 == 0 {
			// Older USART (F1/F4): errors are cleared by reading SR then DR.
			// SR was already read above. If RXNE was set, DR was read in
			// the Receive path. Otherwise do a dummy DR read to complete
			// the clearing sequence.
			uart.rxReg.Get()
		}
	}
}

// WriteByte writes a byte of data to the UART.
func (uart *UART) writeByte(c byte) error {
	// Wait for the transmit data register to be empty before writing, so we
	// don't overwrite a byte that hasn't moved to the shift register yet.
	for !uart.statusReg.HasBits(uart.txEmptyFlag) {
	}
	uart.txReg.Set(uint32(c))
	return nil
}

// flush waits until the USART shift register has finished transmitting the
// last byte (TC = Transmission Complete, bit 6). Without this, Write() returns
// while the final byte is still clocking out on the wire.
func (uart *UART) flush() {
	for !uart.statusReg.HasBits(1 << 6) { // TC bit
	}
}
