/*
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include "board/board.h"

#include <stdint.h>

//
//! High level slave port interface
//! This part of the API can be used for fairly straightforward SPI interactions
//! The assertion and deassertion of the SCS line is automatic
//

//! Scatter Gather tx / rx information
typedef struct SPIScatterGather {
    size_t sg_len; // number of bytes to tx and/or rx
    const void *sg_out; //! may be NULL (0 padding sent)
    void *sg_in; //! may be NULL (nothing saved)
} SPIScatterGather;

typedef bool (*SPIDMACompleteHandler)(const SPISlavePort *slave, void *context);

//! Write byte to slave port and return the corresponding received byte
uint8_t spi_slave_read_write(const SPISlavePort *slave, uint8_t out);

//! Write single data byte to the given slave port
void spi_slave_write(const SPISlavePort *slave, uint8_t out);

//! Read a burst of bytes from given slave port (asserts SCS)
//! 0 bytes are sent to the slave port to prompt the incoming bytes
void spi_slave_burst_read(const SPISlavePort *slave, void *in, size_t len);

//! Write a burst of bytes to the given slave port (asserts SCS)
//! No data is received or waited for
void spi_slave_burst_write(const SPISlavePort *slave, const void *out, size_t len);

//! Transmit and Receive data bytes to and from the SPI Slave Port
//! If out is NULL then 0s are transmitted, if in is NULL incoming data is not saved
void spi_slave_burst_read_write(const SPISlavePort *slave, const void *out, void *in, size_t len);

//! Transmit and Receive data bytes to and from the SPI Slave Port (scatter gather)
void spi_slave_burst_read_write_scatter(const SPISlavePort *slave,
                                       const SPIScatterGather *sc_info,
                                       size_t num_sg);

//! Set (or change) the clock frequency for the given SPI Slave Port (Hz)
void spi_slave_set_frequency(const SPISlavePort *slave, uint32_t frequency_hz);

//! Wait until the SPI Slave is idle
void spi_slave_wait_until_idle_blocking(const SPISlavePort *slave);

//! Gets the peripheral data register address for setting up DMA
uint32_t spi_get_dma_base_address(const SPISlavePort *slave);

//
//! Low level slave port interface
//! This part of the API can be used for slightly more complex SPI operations
//! (such as piecemeal reads or writes).  Assertion and deassertion of SCS
//! is up to the caller.  Asserts in the code will help to ensure that the
//! API is used correctly.
//

//! The general use case for the _ll_ functions:
//!
//! acquire the Slave Port (start peripheral clock and enable SPI)
//!   spi_ll_slave_acquire(SLAVE);
//!   spi_ll_slave_scs_assert(SLAVE);
//! perform (multiple) reads and writes as required
//!   spi_ll_XXXX(SLAVE, ...);
//!   spi_ll_XXXX(SLAVE, ...);
//!   spi_ll_XXXX(SLAVE, ...);
//! release the Slave Port (stop peripheral clock and disable SPI)
//!   spi_ll_slave_scs_deassert(SLAVE);
//!   spi_ll_slave_release(SLAVE);
//!
//! Using the _ll_ routines it is also possible to perform slightly
//! odd SPI transactions such as transmitting while SCS is not asserted
//! or starting/stopping the clock while the SCS is asserted.
//! These routines can also be used to adjust for timing requirements
//! that some devices have.

//! Acquire the SPI device for use by spi_ll_XXXX functions
//! All spi_ll_XXXX have asserts to check for acquisition
//! Note: does not guarantee exclusivity for right now
//! but could easily do one day if we ever share a SPI
//! bus between multiple Slave Ports
void spi_ll_slave_acquire(const SPISlavePort *slave);

//! Release the SPI Slave Port
void spi_ll_slave_release(const SPISlavePort *slave);

//! Assert the SCS for the given SPI Slave Port
void spi_ll_slave_scs_assert(const SPISlavePort *slave);

//! Deassert the SCS for the given SPI slave port
void spi_ll_slave_scs_deassert(const SPISlavePort *slave);

//! Write byte to slave port and return the corresponding received byte
//! It is up to the caller to ensure SCS is asserted correctly
uint8_t spi_ll_slave_read_write(const SPISlavePort *slave, uint8_t out);

//! Write single data byte to the given slave port
//! It is up to the caller to ensure SCS is asserted correctly
void spi_ll_slave_write(const SPISlavePort *slave, uint8_t out);

//! Read a burst of bytes from given slave port
//! 0 bytes are sent to the slave port to prompt the incoming bytes
//! It is up to the caller to ensure SCS is asserted correctly
void spi_ll_slave_burst_read(const SPISlavePort *slave, void *in, size_t len);

//! Write a burst of bytes to the given slave port
//! No data is received or waited for
//! It is up to the caller to ensure SCS is asserted correctly
void spi_ll_slave_burst_write(const SPISlavePort *slave, const void *out, size_t len);

//! Transmit and Receive data bytes to and from the SPI Slave Port
//! If out is NULL then 0s are transmitted, if in is NULL incoming data is not saved
//! It is up to the caller to ensure SCS is asserted correctly
void spi_ll_slave_burst_read_write(const SPISlavePort *slave,
                                   const void *out,
                                   void *in,
                                   size_t len);

//! Transmit and Receive data bytes to and from the SPI Slave Port (scatter gather)
//! It is up to the caller to ensure SCS is asserted correctly
void spi_ll_slave_burst_read_write_scatter(const SPISlavePort *slave,
                                           const SPIScatterGather *sc_info,
                                           size_t num_sg);

//! Reads data from the given slave port via DMA
void spi_ll_slave_read_dma_start(const SPISlavePort *slave, void *in, size_t len,
                                 SPIDMACompleteHandler handler, void *context);

//! Stops the read DMA on the given slave port
void spi_ll_slave_read_dma_stop(const SPISlavePort *slave);

//! Write data to the given slave port via DMA
void spi_ll_slave_write_dma_start(const SPISlavePort *slave, const void *out, size_t len,
                                  SPIDMACompleteHandler handler, void *context);

//! Stops the write DMA on the given slave port
void spi_ll_slave_write_dma_stop(const SPISlavePort *slave);

//! Sends and receives data via DMA on the given slave port. If out is NULL, 0s are transmitted. If
//! in is NULL, incoming data is not saved.
void spi_ll_slave_read_write_dma_start(const SPISlavePort *slave, const void *out, void *in,
                                       size_t len, SPIDMACompleteHandler handler, void *context);

//! Stops the read + write DMA on the given slave port
void spi_ll_slave_read_write_dma_stop(const SPISlavePort *slave);

//! Checks whether a DMA operation is in progress on the given slave port
bool spi_ll_slave_dma_in_progress(const SPISlavePort *slave);

//! The state of the SPI clock lines between transactions is unspecified. Some devices
//! expect the clock to be in a certain state. This routine will drive the line low when
//! enable is true and reconfigure it as a SPI CLK line when false
void spi_ll_slave_drive_clock(const SPISlavePort *slave, bool enable);

//! Clears any errors which may be set
void spi_ll_slave_clear_errors(const SPISlavePort *slave);

//! Initialize a single SPI device instance. Must be called before first use
void spi_slave_port_init(SPISlavePort *device);

//! Deinitializes the SPI device
void spi_slave_port_deinit(const SPISlavePort *slave);

// REVISIT:
// This prototype is used by the roll-your-own SPI drivers.
// It (and the definition) can go away once the new driver
// API is adopted universally.
uint16_t spi_find_prescaler(uint32_t bus_frequency, SpiPeriphClock periph_clock);
