#include "axil_spi.h"
#include "axil_spi_regs.h"

#include <stddef.h>

bss_spi_result bss_spi_set_divider(uintptr_t base, uint32_t half_period_cycles)
{
    if (half_period_cycles == 0 || half_period_cycles > BSS_SPI_DIVIDER_MAX) {
        return BSS_SPI_INVALID_ARGUMENT;
    }
    if ((bss_spi_read32(base + BSS_SPI_REG_STATUS) & BSS_SPI_STATUS_BUSY) != 0) {
        return BSS_SPI_BUSY;
    }

    bss_spi_write32(base + BSS_SPI_REG_DIVIDER, half_period_cycles);
    return BSS_SPI_OK;
}

bss_spi_result bss_spi_transfer_byte(uintptr_t base, uint8_t tx, uint8_t *rx,
                                     uint32_t poll_limit)
{
    if (poll_limit == 0) {
        return BSS_SPI_INVALID_ARGUMENT;
    }
    if ((bss_spi_read32(base + BSS_SPI_REG_STATUS) & BSS_SPI_STATUS_BUSY) != 0) {
        return BSS_SPI_BUSY;
    }

    /* Starting a new frame clears stale DONE in the RTL. Wait for DONE rather
     * than for BUSY to rise, since a fast frame can finish before the first poll. */
    bss_spi_write32(base + BSS_SPI_REG_TXDATA, tx);
    for (uint32_t remaining = poll_limit; remaining != 0; --remaining) {
        const uint32_t status = bss_spi_read32(base + BSS_SPI_REG_STATUS);
        if ((status & (BSS_SPI_STATUS_DONE | BSS_SPI_STATUS_BUSY)) == BSS_SPI_STATUS_DONE) {
            if (rx != NULL) {
                *rx = (uint8_t)(bss_spi_read32(base + BSS_SPI_REG_RXDATA) & BSS_SPI_DATA_MASK);
            }
            /* STATUS is write-one-to-clear. Never read-modify-write it. */
            bss_spi_write32(base + BSS_SPI_REG_STATUS, BSS_SPI_STATUS_DONE);
            return BSS_SPI_OK;
        }
    }
    return BSS_SPI_TIMEOUT;
}
