// SPI transmissions on sam3
//
// Copyright (C) 2018  Petri Honkala <cruwaller@gmail.com>
// Copyright (C) 2018  Florian Heilmann <Florian.Heilmann@gmx.net>
// Copyright (C) 2018  Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.

#include "command.h" // shutdown
#include "gpio.h" // spi_setup
#include "internal.h" // gpio_peripheral
#include "sched.h" // sched_shutdown


/****************************************************************
 * SPI/USART buses and pins
 ****************************************************************/

struct spi_info {
    void *dev;
    uint32_t dev_id;
    uint8_t miso_pin, mosi_pin, sck_pin, rxtx_periph, sck_periph;
};

static const struct spi_info spi_bus[] = {
#if CONFIG_MACH_SAM3X
    { SPI0, ID_SPI0, GPIO('A', 25), GPIO('A', 26), GPIO('A', 27), 'A', 'A' },
    { USART0, ID_USART0, GPIO('A', 10), GPIO('A', 11), GPIO('A', 17), 'A', 'B'},
    { USART1, ID_USART1, GPIO('A', 12), GPIO('A', 13), GPIO('A', 16), 'A', 'A'},
    { USART2, ID_USART2, GPIO('B', 21), GPIO('B', 20), GPIO('B', 24), 'A', 'A'},
#elif CONFIG_MACH_SAM4S
    { SPI, ID_SPI, GPIO('A', 12), GPIO('A', 13), GPIO('A', 14), 'A', 'A' },
    { USART0, ID_USART0, GPIO('A', 5), GPIO('A', 6), GPIO('A', 2), 'A', 'B' },
    { USART1, ID_USART1, GPIO('A', 21), GPIO('A', 22), GPIO('A', 23), 'A', 'A'},
#elif CONFIG_MACH_SAM4E
    { USART0, ID_USART0, GPIO('B', 0), GPIO('B', 1), GPIO('B', 13), 'C', 'C' },
    { USART1, ID_USART1, GPIO('A', 21), GPIO('A', 22), GPIO('A', 23), 'A', 'A'},
    { SPI, ID_SPI, GPIO('A', 12), GPIO('A', 13), GPIO('A', 14), 'A', 'A' },
#endif
};

static int
is_spihw(void *dev)
{
#if CONFIG_MACH_SAM3X
    return dev == SPI0;
#else
    return dev == SPI;
#endif
}

static void
init_pins(uint32_t bus)
{
    const struct spi_info *si = &spi_bus[bus];
    gpio_peripheral(si->sck_pin, si->sck_periph, 0);
    gpio_peripheral(si->miso_pin, si->rxtx_periph, 1);
    gpio_peripheral(si->mosi_pin, si->rxtx_periph, 0);
    enable_pclock(si->dev_id);
}


/****************************************************************
 * SPI hardware
 ****************************************************************/

#define CHANNEL    0 // Use same channel for all

static void
spihw_init(uint32_t bus)
{
    init_pins(bus);
    Spi *pSpi = spi_bus[bus].dev;

    /* Disable SPI */
    pSpi->SPI_CR  = SPI_CR_SPIDIS;
    /* Execute a software reset of the SPI twice */
    pSpi->SPI_CR  = SPI_CR_SWRST;
    pSpi->SPI_CR  = SPI_CR_SWRST;

    pSpi->SPI_MR = ( SPI_MR_MSTR |       // Set master mode
                     SPI_MR_MODFDIS |    // Disable fault detection
                     SPI_MR_PCS(CHANNEL) // Fixes peripheral select
                   );
    pSpi->SPI_IDR = 0xffffffff; // Disable ISRs

    /* Clear Chip Select Registers */
    pSpi->SPI_CSR[0] = 0;
    pSpi->SPI_CSR[1] = 0;
    pSpi->SPI_CSR[2] = 0;
    pSpi->SPI_CSR[3] = 0;

    /* Set basic channel config */
    pSpi->SPI_CSR[CHANNEL] = 0;
    /* Enable SPI */
    pSpi->SPI_CR = SPI_CR_SPIEN;
}

static struct spi_config
spihw_setup(uint32_t bus, uint8_t mode, uint32_t rate)
{
    // Make sure bus is enabled
    spihw_init(bus);

    uint32_t config = 0;
    uint32_t clockDiv;
    if (rate < (CHIP_FREQ_CPU_MAX / 255)) {
        clockDiv = 255;
    } else if (rate >= (CHIP_FREQ_CPU_MAX / 2)) {
        clockDiv = 2;
    } else {
        clockDiv = (CHIP_FREQ_CPU_MAX / (rate + 1)) + 1;
    }

    /****** Will be written to SPI_CSRx register ******/
    // CSAAT  : Chip Select Active After Transfer
    config  = SPI_CSR_CSAAT;
    config |= SPI_CSR_BITS_8_BIT; // TODO: support for SPI_CSR_BITS_16_BIT
    // NOTE: NCPHA is inverted, CPHA normal!!
    switch(mode) {
        case 0:
            config |= SPI_CSR_NCPHA;
            break;
        case 1:
            config |= 0;
            break;
        case 2:
            config |= SPI_CSR_NCPHA;
            config |= SPI_CSR_CPOL;
            break;
        case 3:
            config |= SPI_CSR_CPOL;
            break;
    };

    config |= ((clockDiv & 0xffu) << SPI_CSR_SCBR_Pos);
    return (struct spi_config){ .spidev = spi_bus[bus].dev, .cfg = config };
}

static void
spihw_prepare(struct spi_config config)
{
    Spi *pSpi = config.spidev;
    pSpi->SPI_CSR[CHANNEL] = config.cfg;
}

static void
spihw_transfer(struct spi_config config, uint8_t receive_data
               , uint8_t len, uint8_t *data)
{
    Spi *pSpi = config.spidev;
    if (receive_data) {
        while (len--) {
            pSpi->SPI_TDR = *data;
            // wait for receive register
            while (!(pSpi->SPI_SR & SPI_SR_RDRF))
                ;
            // get data
            *data++ = pSpi->SPI_RDR;
        }
    } else {
        while (len--) {
            pSpi->SPI_TDR = *data++;
            // wait for receive register
            while (!(pSpi->SPI_SR & SPI_SR_RDRF))
                ;
            // read data (to clear RDRF)
            pSpi->SPI_RDR;
        }
    }
}


/****************************************************************
 * USART hardware
 ****************************************************************/

static struct spi_config
usart_setup(uint32_t bus, uint8_t mode, uint32_t rate)
{
    init_pins(bus);
    Usart *p_usart = spi_bus[bus].dev;

    p_usart->US_MR = 0;
    p_usart->US_RTOR = 0;
    p_usart->US_TTGR = 0;

    p_usart->US_CR = US_CR_RSTTX | US_CR_RSTRX | US_CR_TXDIS | US_CR_RXDIS;

    uint32_t br = DIV_ROUND_UP(CHIP_FREQ_CPU_MAX, rate);
    p_usart->US_BRGR = br << US_BRGR_CD_Pos;

    uint32_t reg = US_MR_CHRL_8_BIT |
        US_MR_USART_MODE_SPI_MASTER |
        US_MR_CLKO |
        US_MR_CHMODE_NORMAL;
    switch (mode) {
    case 0:
        reg |= US_MR_CPHA;
        reg &= ~US_MR_CPOL;
        break;
    case 1:
        reg &= ~US_MR_CPHA;
        reg &= ~US_MR_CPOL;
        break;
    case 2:
        reg |= US_MR_CPHA;
        reg |= US_MR_CPOL;
        break;
    case 3:
        reg &= ~US_MR_CPHA;
        reg |= US_MR_CPOL;
        break;
    }

    p_usart->US_MR |= reg;
    p_usart->US_CR = US_CR_RXEN | US_CR_TXEN;
    return (struct spi_config){ .spidev=p_usart, .cfg=p_usart->US_MR };
}

static void
usart_prepare(struct spi_config config)
{
}

static void
usart_transfer(struct spi_config config, uint8_t receive_data
               , uint8_t len, uint8_t *data)
{
    Usart *p_usart = config.spidev;
    if (receive_data) {
        for (uint32_t i = 0; i < len; ++i) {
            uint32_t co = (uint32_t)*data & 0x000000FF;
            while(!(p_usart->US_CSR & US_CSR_TXRDY)) {}
            p_usart->US_THR = US_THR_TXCHR(co);
            uint32_t ci = 0;
            while(!(p_usart->US_CSR & US_CSR_RXRDY)) {}
            ci = p_usart->US_RHR & US_RHR_RXCHR_Msk;
            *data++ = (uint8_t)ci;
        }
    } else {
        for (uint32_t i = 0; i < len; ++i) {
            uint32_t co = (uint32_t)*data & 0x000000FF;
            while(!(p_usart->US_CSR & US_CSR_TXRDY)) {}
            p_usart->US_THR = US_THR_TXCHR(co);
            while(!(p_usart->US_CSR & US_CSR_RXRDY)) {}
            (void)(p_usart->US_RHR & US_RHR_RXCHR_Msk);
            (void)*data++;
        }
    }
}


/****************************************************************
 * Interface
 ****************************************************************/

struct spi_config
spi_setup(uint32_t bus, uint8_t mode, uint32_t rate)
{
    if (bus >= ARRAY_SIZE(spi_bus))
        shutdown("Invalid spi bus");
    if (is_spihw(spi_bus[bus].dev))
        return spihw_setup(bus, mode, rate);
    return usart_setup(bus, mode, rate);
}

void
spi_prepare(struct spi_config config)
{
    if (is_spihw(config.spidev))
        spihw_prepare(config);
    else
        usart_prepare(config);
}

void
spi_transfer(struct spi_config config, uint8_t receive_data
             , uint8_t len, uint8_t *data)
{
    if (is_spihw(config.spidev))
        spihw_transfer(config, receive_data, len, data);
    else
        usart_transfer(config, receive_data, len, data);
}
