/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

#include <string.h>
#include <assert.h>
#include "os/mynewt.h"
#include <hal/hal_spi.h>
#include <hal/hal_gpio.h>
#include "mcu/stm32_hal.h"
#include "mcu/cmsis_nvic.h"

#define SPI_0_ENABLED (MYNEWT_VAL(SPI_0_MASTER) || MYNEWT_VAL(SPI_0_SLAVE))
#define SPI_1_ENABLED (MYNEWT_VAL(SPI_1_MASTER) || MYNEWT_VAL(SPI_1_SLAVE))
#define SPI_2_ENABLED (MYNEWT_VAL(SPI_2_MASTER) || MYNEWT_VAL(SPI_2_SLAVE))
#define SPI_3_ENABLED (MYNEWT_VAL(SPI_3_MASTER) || MYNEWT_VAL(SPI_3_SLAVE))
#define SPI_4_ENABLED (MYNEWT_VAL(SPI_4_MASTER) || MYNEWT_VAL(SPI_4_SLAVE))
#define SPI_5_ENABLED (MYNEWT_VAL(SPI_5_MASTER) || MYNEWT_VAL(SPI_5_SLAVE))

#define SPI_ENABLED (SPI_0_ENABLED || SPI_1_ENABLED || SPI_2_ENABLED || \
                     SPI_3_ENABLED || SPI_4_ENABLED || SPI_5_ENABLED)

#define STM32_HAL_SPI_TIMEOUT (1000)

#define STM32_HAL_SPI_MAX (6)

#if defined(STM32F1) && !defined(HAL_SPI_ERROR_FRE)
/*
 * For some reason HAL_SPI_ERROR_FRE is not defined for STM32F1.
 * It seems that it used to be. Definition is taken from STM32F0 since
 * register bit accessed the same as in very other STM32 MCU.
 */
#define HAL_SPI_ERROR_FRE               (0x00000008U)   /*!< FRE error                              */
#endif

extern HAL_StatusTypeDef HAL_SPI_QueueTransmit(SPI_HandleTypeDef *hspi,
        uint8_t *pData, uint16_t Size);

extern HAL_StatusTypeDef HAL_SPI_Slave_Queue_TransmitReceive(
        SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
        uint16_t Size);

extern HAL_StatusTypeDef HAL_SPI_Transmit_IT_Custom(SPI_HandleTypeDef *hspi,
        uint8_t *pData, uint16_t Size);

extern HAL_StatusTypeDef HAL_SPI_TransmitReceive_IT_Custom(
        SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData,
        uint16_t Size);

struct stm32_hal_spi {
    SPI_HandleTypeDef handle;
    uint8_t slave:1;                  /* slave or master? */
    uint8_t tx_in_prog:1;             /* slave: tx'ing user data not def */
    uint8_t selected:1;               /* slave: if we see SS */
    uint8_t def_char[4];              /* slave: default data to tx */
    struct stm32_hal_spi_cfg cfg;
    hal_spi_txrx_cb txrx_cb_func;     /* callback function */
    void            *txrx_cb_arg;     /* callback arguments */
};

static struct stm32_spi_stat {
    uint32_t irq;
    uint32_t ss_irq;
    uint32_t tx;
    uint32_t eovf;
    uint32_t emodf;
    uint32_t efre;
} spi_stat;

#if SPI_ENABLED
static void spi_irq_handler(struct stm32_hal_spi *spi);
#endif

#if SPI_0_ENABLED
struct stm32_hal_spi stm32_hal_spi0;
#endif
#if SPI_1_ENABLED
struct stm32_hal_spi stm32_hal_spi1;
#endif
#if SPI_2_ENABLED
struct stm32_hal_spi stm32_hal_spi2;
#endif
#if SPI_3_ENABLED
struct stm32_hal_spi stm32_hal_spi3;
#endif
#if SPI_4_ENABLED
struct stm32_hal_spi stm32_hal_spi4;
#endif
#if SPI_5_ENABLED
struct stm32_hal_spi stm32_hal_spi5;
#endif

static struct stm32_hal_spi *stm32_hal_spis[STM32_HAL_SPI_MAX] = {
#if SPI_0_ENABLED
    &stm32_hal_spi0,
#else
    NULL,
#endif
#if SPI_1_ENABLED
    &stm32_hal_spi1,
#else
    NULL,
#endif
#if SPI_2_ENABLED
    &stm32_hal_spi2,
#else
    NULL,
#endif
#if SPI_3_ENABLED
    &stm32_hal_spi3,
#else
    NULL,
#endif
#if SPI_4_ENABLED
    &stm32_hal_spi4,
#else
    NULL,
#endif
#if SPI_5_ENABLED
    &stm32_hal_spi5,
#else
    NULL,
#endif
};

#define STM32_HAL_SPI_RESOLVE(__n, __v)                          \
    if ((__n) >= STM32_HAL_SPI_MAX) {                            \
        rc = -1;                                                 \
        goto err;                                                \
    }                                                            \
    (__v) = (struct stm32_hal_spi *) stm32_hal_spis[(__n)];      \
    if ((__v) == NULL) {                                         \
        rc = -1;                                                 \
        goto err;                                                \
    }

static IRQn_Type
stm32_resolve_spi_irq(SPI_HandleTypeDef *hspi)
{
    uintptr_t spi = (uintptr_t)hspi->Instance;

    switch (spi) {
#if SPI_0_ENABLED
    case (uintptr_t)SPI1:
        return SPI1_IRQn;
#endif
#if SPI_1_ENABLED
    case (uintptr_t)SPI2:
        return SPI2_IRQn;
#endif
#if SPI_2_ENABLED
    case (uintptr_t)SPI3:
        return SPI3_IRQn;
#endif
#if SPI_3_ENABLED
    case (uintptr_t)SPI4:
        return SPI4_IRQn;
#endif
#if SPI_4_ENABLED
    case (uintptr_t)SPI5:
        return SPI5_IRQn;
#endif
#if SPI_5_ENABLED
    case (uintptr_t)SPI6:
        return SPI6_IRQn;
#endif
    default:
        assert(0);
    }
}

/*
 * SPI master IRQ handler.
 */
#if SPI_ENABLED
static void
spim_irq_handler(struct stm32_hal_spi *spi)
{
    if (spi->handle.TxXferCount == 0 && spi->handle.RxXferCount == 0) {
        if (spi->txrx_cb_func) {
            spi->txrx_cb_func(spi->txrx_cb_arg, spi->handle.TxXferSize);
        }
    }
}
#endif

/*
 * SPI slave IRQ handler.
 */
#if SPI_ENABLED
static void
spis_irq_handler(struct stm32_hal_spi *spi)
{
    if (spi->tx_in_prog) {
        if (spi->handle.TxXferCount == 0 && spi->handle.RxXferCount == 0) {
            /*
             * If finished with data tx, start transmitting default char
             */
            spi->tx_in_prog = 0;

            HAL_SPI_Transmit_IT_Custom(&spi->handle, spi->def_char, 2);

            if (spi->txrx_cb_func) {
                spi->txrx_cb_func(spi->txrx_cb_arg, spi->handle.TxXferSize);
            }
        }
    } else {
        /*
         * Reset TX pointer within default data.
         */
        spi->handle.pTxBuffPtr = spi->def_char;
        spi->handle.TxXferCount = 2;
    }
}
#endif

/*
 * Common IRQ handler for both master and slave.
 */
#if SPI_ENABLED
static void
spi_irq_handler(struct stm32_hal_spi *spi)
{
    uint32_t err;

    spi_stat.irq++;

    HAL_SPI_IRQHandler(&spi->handle);
    err = spi->handle.ErrorCode;
    if (err) {
        if (err & HAL_SPI_ERROR_OVR) {
            spi_stat.eovf++;
        }
        if (err & HAL_SPI_ERROR_MODF) {
            spi_stat.emodf++;
        }
        if (err & HAL_SPI_ERROR_FRE) {
            spi_stat.efre++;
        }
        spi->handle.ErrorCode = 0;
    }
    if (!spi->slave) {
        spim_irq_handler(spi);
    } else {
        spis_irq_handler(spi);
    }
}
#endif

#if !defined(SPI_IT_TXE) && defined(SPI_IT_TXP)
#define SPI_IT_TXE                      SPI_IT_TXP
#endif
#if !defined(SPI_IT_RXNE) && defined(SPI_IT_RXP)
#define SPI_IT_RXNE                     SPI_IT_RXP
#define SPI_SR_RXNE                     SPI_SR_RXP
#endif

/*
 * GPIO interrupt when slave gets selected/deselected.
 */
static void
spi_ss_isr(void *arg)
{
    struct stm32_hal_spi *spi = (struct stm32_hal_spi *)arg;
    int ss;
    int len;
    uint16_t reg;

    spi_stat.ss_irq++;
    ss = hal_gpio_read(spi->cfg.ss_pin);
    if (ss == 0 && !spi->selected) {
        /*
         * We're now seeing chip select. Enable SPI, SPI interrupts.
         */
        if (spi->tx_in_prog) {
            __HAL_SPI_ENABLE_IT(&spi->handle,
              SPI_IT_RXNE | SPI_IT_TXE | SPI_IT_ERR);
        } else {
            __HAL_SPI_ENABLE_IT(&spi->handle, SPI_IT_TXE | SPI_IT_ERR);
        }
        reg = spi->handle.Instance->CR1;
        reg &= ~SPI_CR1_SSI;
        reg |= SPI_CR1_SPE;
        spi->handle.Instance->CR1 = reg;
        spi->selected = 1;
    }
    if (ss == 1 && spi->selected) {
        /*
         * Chip select done. Check whether there's pending data to RX.
         */
#if MYNEWT_VAL(MCU_STM32H7)
        if (spi->handle.Instance->SR & SPI_SR_RXP && spi->handle.RxISR) {
            spi->handle.RxISR(&spi->handle);
        }
#else
        if (spi->handle.Instance->SR & SPI_SR_RXNE && spi->handle.RxISR) {
            spi->handle.RxISR(&spi->handle);
        }
#endif
        /*
         * Disable SPI.
         */
        reg = spi->handle.Instance->CR1;
        reg &= ~SPI_CR1_SPE;
        reg |= SPI_CR1_SSI;
        spi->handle.Instance->CR1 = reg;

        __HAL_SPI_DISABLE_IT(&spi->handle, SPI_IT_RXNE|SPI_IT_TXE|SPI_IT_ERR);

        len = spi->handle.RxXferSize - spi->handle.RxXferCount;
        if (len) {
            /*
             * If some data was clocked out, reset to start sending
             * default data and call callback, if user was waiting for
             * data.
             */

            spi->handle.State = HAL_SPI_STATE_READY;

            HAL_SPI_QueueTransmit(&spi->handle, spi->def_char, 2);

            if (spi->tx_in_prog) {
                spi->tx_in_prog = 0;

                if (spi->txrx_cb_func) {
                    spi->txrx_cb_func(spi->txrx_cb_arg, len);
                }
            }
        }
        spi->selected = 0;
    }
}

#if SPI_0_ENABLED
static void
spi1_irq_handler(void)
{
    spi_irq_handler(stm32_hal_spis[0]);
}
#endif

#if SPI_1_ENABLED
static void
spi2_irq_handler(void)
{
    spi_irq_handler(stm32_hal_spis[1]);
}
#endif

#if SPI_2_ENABLED
static void
spi3_irq_handler(void)
{
    spi_irq_handler(stm32_hal_spis[2]);
}
#endif

#if SPI_3_ENABLED
static void
spi4_irq_handler(void)
{
    spi_irq_handler(stm32_hal_spis[3]);
}
#endif

#if SPI_4_ENABLED
static void
spi5_irq_handler(void)
{
    spi_irq_handler(stm32_hal_spis[4]);
}
#endif


#if SPI_5_ENABLED
static void
spi6_irq_handler(void)
{
    spi_irq_handler(stm32_hal_spis[5]);
}
#endif

uint32_t
stm32_resolve_spi_irq_handler(SPI_HandleTypeDef *hspi)
{
    switch((uintptr_t)hspi->Instance) {
#if SPI_0_ENABLED
    case (uintptr_t)SPI1:
        return (uint32_t)&spi1_irq_handler;
#endif
#if SPI_1_ENABLED
    case (uintptr_t)SPI2:
        return (uint32_t)&spi2_irq_handler;
#endif
#if SPI_2_ENABLED
    case (uintptr_t)SPI3:
        return (uint32_t)&spi3_irq_handler;
#endif
#if SPI_3_ENABLED
    case (uintptr_t)SPI4:
        return (uint32_t)&spi4_irq_handler;
#endif
#if SPI_4_ENABLED
    case (uintptr_t)SPI5:
        return (uint32_t)&spi5_irq_handler;
#endif
#if SPI_5_ENABLED
    case (uintptr_t)SPI6:
        return (uint32_t)&spi6_irq_handler;
#endif
    default:
        assert(0);
    }
}

int
hal_spi_init(int spi_num, void *usercfg, uint8_t spi_type)
{
    struct stm32_hal_spi *spi;
    int rc;

    /* Check for valid arguments */
    rc = -1;
    if (usercfg == NULL) {
        goto err;
    }

    if ((spi_type != HAL_SPI_TYPE_MASTER) && (spi_type != HAL_SPI_TYPE_SLAVE)) {
        goto err;
    }

    /* Allow user to specify default init settings for the SPI.
     * This can be done in BSP, so that only the generic SPI settings
     * are passed to the user configure() call.
     */
    STM32_HAL_SPI_RESOLVE(spi_num, spi);

    spi->cfg = *(struct stm32_hal_spi_cfg *)usercfg;
    spi->slave = (spi_type == HAL_SPI_TYPE_SLAVE);
    switch (spi_num) {
#if SPI_0_ENABLED
    case 0:
        spi->handle.Instance = SPI1;
        break;
#endif
#if SPI_1_ENABLED
    case 1:
        spi->handle.Instance = SPI2;
        break;
#endif
#if SPI_2_ENABLED
    case 2:
        spi->handle.Instance = SPI3;
        break;
#endif
#if SPI_3_ENABLED
    case 3:
        spi->handle.Instance = SPI4;
        break;
#endif
#if SPI_4_ENABLED
    case 4:
        spi->handle.Instance = SPI5;
        break;
#endif
#if SPI_5_ENABLED
    case 5:
        spi->handle.Instance = SPI6;
        break;
#endif
    default:
        rc = -1;
        goto err;
    }

    return (0);
err:
    return (rc);
}

static int
stm32_spi_resolve_prescaler(uint8_t spi_num, uint32_t baudrate, uint32_t *prescaler)
{
    uint32_t candidate_br;
    uint32_t apbfreq;
    int i;

    /* SPIx {1,4,5,6} use PCLK2 on the STM32F4/F7, otherwise use PCKL1.
     * The numbers in the switch below are offset by 1, because the HALs index
     * SPI ports from 0.
     */
    switch (spi_num) {
#if !MYNEWT_VAL(MCU_STM32F0) && !MYNEWT_VAL(MCU_STM32G0)
    case 0:
    case 3:
    case 4:
    case 5:
        apbfreq = HAL_RCC_GetPCLK2Freq();
        break;
#endif
    default:
        apbfreq = HAL_RCC_GetPCLK1Freq();
        break;
    }

    if (baudrate == 0) {
        *prescaler = 0;
        return 0;
    }

    /* Calculate best-fit prescaler: divide the clock by each subsequent
     * prescaler until we reach the highest prescaler that generates at
     * _most_ the baudrate.
     */
    *prescaler = SPI_BAUDRATEPRESCALER_256;
    for (i = 0; i < 8; i++) {
        candidate_br = apbfreq >> (i + 1);
        if (candidate_br <= baudrate) {
#if defined(SPI_CR1_BR_Pos)
            *prescaler = i << SPI_CR1_BR_Pos;
#else
            *prescaler = i << SPI_CFG1_MBR_Pos;
#endif
            break;
        }
    }

    return (0);
}

/**
 * Sets the txrx callback (executed at interrupt context) when the
 * buffer is transferred by the master or the slave using the non-blocking API.
 * Cannot be called when the spi is enabled. This callback will also be called
 * when chip select is de-asserted on the slave.
 *
 * NOTE: This callback is only used for the non-blocking interface and must
 * be called prior to using the non-blocking API.
 *
 * @param spi_num   SPI interface on which to set callback
 * @param txrx      Callback function
 * @param arg       Argument to be passed to callback function
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_set_txrx_cb(int spi_num, hal_spi_txrx_cb txrx_cb, void *arg)
{
    struct stm32_hal_spi *spi;
    int rc = 0;
    int sr;

    STM32_HAL_SPI_RESOLVE(spi_num, spi);

    __HAL_DISABLE_INTERRUPTS(sr);
    spi->txrx_cb_func = txrx_cb;
    spi->txrx_cb_arg = arg;
    __HAL_ENABLE_INTERRUPTS(sr);
err:
    return rc;
}

/**
 * Enables the SPI. This does not start a transmit or receive operation;
 * it is used for power mgmt. Cannot be called when a SPI transfer is in
 * progress.
 *
 * @param spi_num
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_enable(int spi_num)
{
    struct stm32_hal_spi *spi;
    int rc;

    rc = 0;
    STM32_HAL_SPI_RESOLVE(spi_num, spi);

    __HAL_SPI_ENABLE(&spi->handle);
err:
    return rc;
}

/**
 * Disables the SPI. Used for power mgmt. It will halt any current SPI transfers
 * in progress.
 *
 * @param spi_num
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_disable(int spi_num)
{
    struct stm32_hal_spi *spi;
    int rc;

    rc = 0;
    STM32_HAL_SPI_RESOLVE(spi_num, spi);

    __HAL_SPI_DISABLE(&spi->handle);
err:
    return rc;
}

int
hal_spi_config(int spi_num, struct hal_spi_settings *settings)
{
    struct stm32_hal_spi *spi;
    struct stm32_hal_spi_cfg *cfg;
    SPI_InitTypeDef *init;
    GPIO_InitTypeDef gpio;
    uint32_t gpio_speed;
    IRQn_Type irq;
    uint32_t prescaler;
    int rc;
    int sr;

    __HAL_DISABLE_INTERRUPTS(sr);
    STM32_HAL_SPI_RESOLVE(spi_num, spi);

    init = &spi->handle.Init;

    cfg = &spi->cfg;

    if (!spi->slave) {
        spi->handle.Init.NSS = SPI_NSS_HARD_OUTPUT;
        spi->handle.Init.Mode = SPI_MODE_MASTER;
    } else {
        spi->handle.Init.NSS = SPI_NSS_SOFT;
        spi->handle.Init.Mode = SPI_MODE_SLAVE;
    }

    gpio.Mode = GPIO_MODE_AF_PP;
    gpio.Pull = GPIO_NOPULL;

    /* TODO: also VERY_HIGH for STM32L1x */
    if (settings->baudrate <= 2000) {
        gpio_speed = GPIO_SPEED_FREQ_LOW;
    } else if (settings->baudrate <= 12500) {
        gpio_speed = GPIO_SPEED_FREQ_MEDIUM;
    } else {
        gpio_speed = GPIO_SPEED_FREQ_HIGH;
    }

    /* Enable the clocks for this SPI */
    switch (spi_num) {
#if SPI_0_ENABLED
    case 0:
        __HAL_RCC_SPI1_CLK_ENABLE();
#if !MYNEWT_VAL(MCU_STM32F1)
    #if !MYNEWT_VAL(MCU_STM32L0) && !MYNEWT_VAL(MCU_STM32F0) && !MYNEWT_VAL(MCU_STM32G0)
        gpio.Alternate = GPIO_AF5_SPI1;
    #else
        gpio.Alternate = GPIO_AF0_SPI1;
    #endif
#endif
        break;
#endif
#if SPI_1_ENABLED
    case 1:
        __HAL_RCC_SPI2_CLK_ENABLE();
#if !MYNEWT_VAL(MCU_STM32F1)
    #if !MYNEWT_VAL(MCU_STM32L0) && !MYNEWT_VAL(MCU_STM32F0) && !MYNEWT_VAL(MCU_STM32G0)
        gpio.Alternate = GPIO_AF5_SPI2;
    #else
        gpio.Alternate = GPIO_AF0_SPI2;
    #endif
#endif
        break;
#endif
#if SPI_2_ENABLED
    case 2:
        __HAL_RCC_SPI3_CLK_ENABLE();
#if MYNEWT_VAL(MCU_STM32G0)
        gpio.Alternate = GPIO_AF4_SPI3;
#elif !MYNEWT_VAL(MCU_STM32F1)
        gpio.Alternate = GPIO_AF6_SPI3;
#endif
        break;
#endif
#if SPI_3_ENABLED
    case 3:
        __HAL_RCC_SPI4_CLK_ENABLE();
#if !MYNEWT_VAL(MCU_STM32F1)
        gpio.Alternate = GPIO_AF5_SPI4;
#endif
        break;
#endif
#if SPI_4_ENABLED
    case 4:
        __HAL_RCC_SPI5_CLK_ENABLE();
#if defined(GPIO_AF6_SPI5)
        gpio.Alternate = GPIO_AF6_SPI5;
#elif defined(GPIO_AF5_SPI5)
        gpio.Alternate = GPIO_AF5_SPI5;
#endif
        break;
#endif
#if SPI_5_ENABLED
    case 5:
        __HAL_RCC_SPI6_CLK_ENABLE();
#if !MYNEWT_VAL(MCU_STM32F1)
        gpio.Alternate = GPIO_AF5_SPI6;
#endif
        break;
#endif
   default:
        assert(0);
        rc = -1;
        goto err;
    }

    if (!spi->slave) {
        if (settings->data_mode == HAL_SPI_MODE2 ||
          settings->data_mode == HAL_SPI_MODE3) {
            gpio.Pull = GPIO_PULLUP;
        } else {
            gpio.Pull = GPIO_PULLDOWN;
        }
    }

    /* NOTE: Errata ES0125: STM32L100x6/8/B, STM32L151x6/8/B and
     * STM32L152x6/8/B ultra-low-power device limitations.
     *
     * 2.6.6 - Corrupted last bit of data and or CRC, received in Master
     * mode with delayed SCK feedback
     *
     * This driver is always using very high speed for SCK on STM32L1x
     */

#if defined(STM32L152xC)
    if (!spi->slave) {
        gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    } else {
        gpio.Speed = gpio_speed;
    }
#else
    gpio.Speed = gpio_speed;
#endif

    rc = hal_gpio_init_stm(cfg->sck_pin, &gpio);
    if (rc != 0) {
        goto err;
    }

#if defined(STM32L152xC)
    if (!spi->slave) {
        gpio.Speed = gpio_speed;
    }
#endif

    if (!spi->slave) {
        gpio.Pull = GPIO_NOPULL;
    } else {
        gpio.Mode = GPIO_MODE_AF_OD;
    }

    rc = hal_gpio_init_stm(cfg->mosi_pin, &gpio);
    if (rc != 0) {
        goto err;
    }
    if (!spi->slave) {
        gpio.Mode = GPIO_MODE_AF_OD;
    } else {
        gpio.Mode = GPIO_MODE_AF_PP;
    }
    rc = hal_gpio_init_stm(cfg->miso_pin, &gpio);
    if (rc != 0) {
        goto err;
    }

    switch (settings->data_mode) {
    case HAL_SPI_MODE0:
        init->CLKPolarity = SPI_POLARITY_LOW;
        init->CLKPhase = SPI_PHASE_1EDGE;
        break;
    case HAL_SPI_MODE1:
        init->CLKPolarity = SPI_POLARITY_LOW;
        init->CLKPhase = SPI_PHASE_2EDGE;
        break;
    case HAL_SPI_MODE2:
        init->CLKPolarity = SPI_POLARITY_HIGH;
        init->CLKPhase = SPI_PHASE_1EDGE;
        break;
    case HAL_SPI_MODE3:
        init->CLKPolarity = SPI_POLARITY_HIGH;
        init->CLKPhase = SPI_PHASE_2EDGE;
        break;
    default:
        rc = -1;
        goto err;
    }

    switch (settings->data_order) {
    case HAL_SPI_MSB_FIRST:
        init->FirstBit = SPI_FIRSTBIT_MSB;
        break;
    case HAL_SPI_LSB_FIRST:
        init->FirstBit = SPI_FIRSTBIT_LSB;
        break;
    default:
        rc = -1;
        goto err;
    }

    switch (settings->word_size) {
    case HAL_SPI_WORD_SIZE_8BIT:
        init->DataSize = SPI_DATASIZE_8BIT;
        break;
    case HAL_SPI_WORD_SIZE_9BIT:
        init->DataSize = SPI_DATASIZE_16BIT;
        break;
    default:
        rc = -1;
        goto err;
    }

    rc = stm32_spi_resolve_prescaler(spi_num, settings->baudrate * 1000, &prescaler);
    if (rc != 0) {
        goto err;
    }

    init->BaudRatePrescaler = prescaler;

    /* Add default values */
    init->Direction = SPI_DIRECTION_2LINES;
    init->TIMode = SPI_TIMODE_DISABLE;
    init->CRCCalculation = SPI_CRCCALCULATION_DISABLE;
    init->CRCPolynomial = 1;
#ifdef SPI_NSS_PULSE_DISABLE
    init->NSSPMode = SPI_NSS_PULSE_DISABLE;
#endif
#ifdef SPI_MASTER_KEEP_IO_STATE_ENABLE
    init->MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE;
#endif

    irq = stm32_resolve_spi_irq(&spi->handle);
    NVIC_SetPriority(irq, cfg->irq_prio);
    NVIC_SetVector(irq, stm32_resolve_spi_irq_handler(&spi->handle));
    NVIC_EnableIRQ(irq);

    /* Init, Enable */
    rc = HAL_SPI_Init(&spi->handle);
    if (rc != 0) {
        goto err;
    }
    if (spi->slave) {
        hal_spi_slave_set_def_tx_val(spi_num, 0);
        rc = hal_gpio_irq_init(cfg->ss_pin, spi_ss_isr, spi, HAL_GPIO_TRIG_BOTH,
                               HAL_GPIO_PULL_UP);
        spi_ss_isr(spi);
    }
    __HAL_ENABLE_INTERRUPTS(sr);
    return (0);
err:
    __HAL_ENABLE_INTERRUPTS(sr);
    return (rc);
}

int
hal_spi_txrx_noblock(int spi_num, void *txbuf, void *rxbuf, int len)
{
    struct stm32_hal_spi *spi;
    int rc;
    int sr;

    STM32_HAL_SPI_RESOLVE(spi_num, spi);

    spi_stat.tx++;
    rc = -1;
    __HAL_DISABLE_INTERRUPTS(sr);
    if (!spi->slave) {
        rc = HAL_SPI_TransmitReceive_IT_Custom(&spi->handle, txbuf, rxbuf, len);
    } else {
        /*
         * Slave: if selected, start transmitting new data.
         * If not selected, queue it for transmission.
         */
        spi->handle.State = HAL_SPI_STATE_READY;
        if (spi->selected) {
            rc = HAL_SPI_TransmitReceive_IT_Custom(&spi->handle, txbuf, rxbuf, len);
        } else {
            rc = HAL_SPI_Slave_Queue_TransmitReceive(&spi->handle, txbuf, rxbuf,
              len);
        }
        if (rc == 0) {
            spi->tx_in_prog = 1;
        }
    }
    __HAL_ENABLE_INTERRUPTS(sr);
err:
    return (rc);
}

/**
 * Sets the default value transferred by the slave. Not valid for master
 *
 * @param spi_num SPI interface to use
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_slave_set_def_tx_val(int spi_num, uint16_t val)
{
    struct stm32_hal_spi *spi;
    int rc;
    int sr;
    int i;

    STM32_HAL_SPI_RESOLVE(spi_num, spi);

    if (spi->slave) {
        rc = 0;
        __HAL_DISABLE_INTERRUPTS(sr);
        if (spi->handle.Init.DataSize == SPI_DATASIZE_8BIT) {
            for (i = 0; i < 4; i++) {
                ((uint8_t *)spi->def_char)[i] = val;
            }
        } else {
            for (i = 0; i < 2; i++) {
                ((uint16_t *)spi->def_char)[i] = val;
            }
        }
        if (!spi->tx_in_prog) {
            /*
             * Replaces the current default char in tx buffer register.
             */
            spi->handle.State = HAL_SPI_STATE_READY;
            rc = HAL_SPI_QueueTransmit(&spi->handle, spi->def_char, 2);
            assert(rc == 0);
        }
        __HAL_ENABLE_INTERRUPTS(sr);
    } else {
        rc = -1;
    }
err:
    return rc;
}

/**
 * Blocking call to send a value on the SPI. Returns the value received from the
 * SPI slave.
 *
 * MASTER: Sends the value and returns the received value from the slave.
 * SLAVE: Invalid API. Returns 0xFFFF
 *
 * @param spi_num   Spi interface to use
 * @param val       Value to send
 *
 * @return uint16_t Value received on SPI interface from slave. Returns 0xFFFF
 * if called when the SPI is configured to be a slave
 */
uint16_t hal_spi_tx_val(int spi_num, uint16_t val)
{
    int rc;
    struct stm32_hal_spi *spi;
    uint16_t retval = 0xFFFF;
    int len;
    int sr;

    STM32_HAL_SPI_RESOLVE(spi_num, spi);
    if (spi->slave) {
        goto err;
    }
    if (spi->handle.Init.DataSize == SPI_DATASIZE_8BIT) {
        len = sizeof(uint8_t);
    } else {
        len = sizeof(uint16_t);
    }
    __HAL_DISABLE_INTERRUPTS(sr);
    spi_stat.tx++;
    retval = 0;
    rc = HAL_SPI_TransmitReceive(&spi->handle,(uint8_t *)&val,
                                 (uint8_t *)&retval, len,
                                 STM32_HAL_SPI_TIMEOUT);
    __HAL_ENABLE_INTERRUPTS(sr);
    if (rc != HAL_OK) {
        retval = 0xFFFF;
    }

err:
    return retval;
}

/**
 * Blocking interface to send a buffer and store the received values from the
 * slave. The transmit and receive buffers are either arrays of 8-bit (uint8_t)
 * values or 16-bit values depending on whether the spi is configured for 8 bit
 * data or more than 8 bits per value. The 'cnt' parameter is the number of
 * 8-bit or 16-bit values. Thus, if 'cnt' is 10, txbuf/rxbuf would point to an
 * array of size 10 (in bytes) if the SPI is using 8-bit data; otherwise
 * txbuf/rxbuf would point to an array of size 20 bytes (ten, uint16_t values).
 *
 * NOTE: these buffers are in the native endian-ness of the platform.
 *
 *     MASTER: master sends all the values in the buffer and stores the
 *             stores the values in the receive buffer if rxbuf is not NULL.
 *             The txbuf parameter cannot be NULL.
 *     SLAVE: cannot be called for a slave; returns -1
 *
 * @param spi_num   SPI interface to use
 * @param txbuf     Pointer to buffer where values to transmit are stored.
 * @param rxbuf     Pointer to buffer to store values received from peer.
 * @param cnt       Number of 8-bit or 16-bit values to be transferred.
 *
 * @return int 0 on success, non-zero error code on failure.
 */
int
hal_spi_txrx(int spi_num, void *txbuf, void *rxbuf, int len)
{
    int rc;
    struct stm32_hal_spi *spi;
    int sr;

    rc = -1;
    if (!len) {
        goto err;
    }
    STM32_HAL_SPI_RESOLVE(spi_num, spi);
    if (spi->slave) {
        goto err;
    }
    __HAL_DISABLE_INTERRUPTS(sr);
    spi_stat.tx++;
    __HAL_ENABLE_INTERRUPTS(sr);
    if (rxbuf) {
        rc = HAL_SPI_TransmitReceive(&spi->handle, (uint8_t *)txbuf,
                                     (uint8_t *)rxbuf, len,
                                     STM32_HAL_SPI_TIMEOUT);
    } else {
        rc = HAL_SPI_Transmit(&spi->handle, (uint8_t *)txbuf,
                              len, STM32_HAL_SPI_TIMEOUT);
    }
    if (rc != HAL_OK) {
        rc = -1;
        goto err;
    }
    rc = 0;
err:
    return rc;
}

int
hal_spi_abort(int spi_num)
{
    int rc;
    struct stm32_hal_spi *spi;
    int sr;

    rc = 0;
    STM32_HAL_SPI_RESOLVE(spi_num, spi);
    if (spi->slave) {
        goto err;
    }
    __HAL_DISABLE_INTERRUPTS(sr);
    spi->handle.State = HAL_SPI_STATE_READY;
    __HAL_SPI_DISABLE_IT(&spi->handle, SPI_IT_TXE | SPI_IT_RXNE | SPI_IT_ERR);
    spi->handle.Instance->CR1 &= ~SPI_CR1_SPE;
    __HAL_ENABLE_INTERRUPTS(sr);
err:
    return rc;
}

int
hal_spi_init_hw(uint8_t spi_num, uint8_t spi_type,
                const struct hal_spi_hw_settings *cfg)
{
    int rc = SYS_EINVAL;
    struct stm32_hal_spi *spi;
    struct stm32_hal_spi_cfg hal_cfg;

    STM32_HAL_SPI_RESOLVE(spi_num, spi);
    if (spi->slave) {
        rc = SYS_ENOTSUP;
        goto err;
    }
    hal_cfg.sck_pin = cfg->pin_sck;
    hal_cfg.mosi_pin = cfg->pin_mosi;
    hal_cfg.miso_pin = cfg->pin_miso;
    hal_cfg.ss_pin = cfg->pin_ss;
    hal_cfg.irq_prio = 2;
    rc = hal_spi_init(spi_num, &hal_cfg, spi_type);
err:
    return rc;
}
