Newer
Older
mbed-os / targets / TARGET_Cypress / TARGET_PSOC6 / mtb-hal-cat1 / source / cyhal_spi.c
@Dustin Crossman Dustin Crossman on 4 Jun 2021 32 KB Fix file modes.
/***************************************************************************//**
* \file cyhal_spi.c
*
* \brief
* Provides a high level interface for interacting with the Cypress SPI. This is
* a wrapper around the lower level PDL API.
*
********************************************************************************
* \copyright
* Copyright 2018-2021 Cypress Semiconductor Corporation
* SPDX-License-Identifier: Apache-2.0
*
* 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.
*******************************************************************************/

#include <stdlib.h>
#include <string.h>
#include "cyhal_spi.h"
#include "cyhal_scb_common.h"
#include "cyhal_gpio.h"
#include "cyhal_system_impl.h"
#include "cyhal_hwmgr.h"
#include "cyhal_system.h"
#include "cyhal_syspm.h"
#include "cyhal_clock.h"

#if defined(CY_IP_MXSCB) || defined(CY_IP_M0S8SCB)

#if defined(__cplusplus)
extern "C"
{
#endif

#define _CYHAL_SPI_DEFAULT_SPEED            100000

#define _CYHAL_SPI_OVERSAMPLE_MIN           4
#define _CYHAL_SPI_OVERSAMPLE_MAX           16
#define _CYHAL_SPI_SSEL_NUM                 4

#define _CYHAL_SPI_PENDING_NONE             0
#define _CYHAL_SPI_PENDING_RX               1
#define _CYHAL_SPI_PENDING_TX               2
#define _CYHAL_SPI_PENDING_TX_RX            3

#define _CYHAL_SPI_SSEL_ACTIVATE            true
#define _CYHAL_SPI_SSEL_DEACTIVATE          false

/* Default SPI configuration */
static const cy_stc_scb_spi_config_t _cyhal_spi_default_config =
{
    .spiMode                  = CY_SCB_SPI_MASTER,
    .subMode                  = CY_SCB_SPI_MOTOROLA,
    .sclkMode                 = CY_SCB_SPI_CPHA0_CPOL0,
    .oversample               = _CYHAL_SPI_OVERSAMPLE_MIN,
    .rxDataWidth              = 8,
    .txDataWidth              = 8,
    .enableMsbFirst           = true,
    .enableFreeRunSclk        = false,
    .enableInputFilter        = false,
    .enableMisoLateSample     = true,
    .enableTransferSeperation = false,
    .enableWakeFromSleep      = false,
    .ssPolarity               = CY_SCB_SPI_ACTIVE_LOW,
    .rxFifoTriggerLevel       = 0,
    .rxFifoIntEnableMask      = 0,
    .txFifoTriggerLevel       = 0,
    .txFifoIntEnableMask      = 0,
    .masterSlaveIntEnableMask = 0
};

static void _cyhal_ssel_switch_state(cyhal_spi_t *obj, uint8_t ssel_idx, bool ssel_activate);

static cy_rslt_t _cyhal_spi_int_frequency(cyhal_spi_t *obj, uint32_t hz, uint8_t *over_sample_val)
{
    CY_ASSERT(NULL != obj);
    cy_rslt_t result = CY_RSLT_SUCCESS;
    uint8_t oversample_value;
    uint32_t divider_value;
    uint32_t last_diff = 0xFFFFFFFFU;
    uint8_t last_ovrsmpl_val = 0;
    uint32_t last_dvdr_val = 0;
    uint32_t oversampled_freq = 0;
    uint32_t divided_freq = 0;
    uint32_t diff = 0;

    Cy_SysClk_PeriphDisableDivider((cy_en_divider_types_t)obj->clock.block, obj->clock.channel);

#if defined(COMPONENT_CAT1A) || defined(COMPONENT_CAT1B)
    uint32_t peri_freq = Cy_SysClk_ClkPeriGetFrequency();
#elif defined(COMPONENT_CAT2)
    uint32_t peri_freq = Cy_SysClk_ClkSysGetFrequency();
#endif
    if (!obj->is_slave)
    {
        for (oversample_value = _CYHAL_SPI_OVERSAMPLE_MIN; oversample_value <= _CYHAL_SPI_OVERSAMPLE_MAX; oversample_value++)
        {
            oversampled_freq = hz * oversample_value;
            if ((hz * oversample_value > peri_freq) && (_CYHAL_SPI_OVERSAMPLE_MIN == oversample_value))
            {
                return CYHAL_SPI_RSLT_CLOCK_ERROR;
            }
            else if (hz * oversample_value > peri_freq)
            {
                continue;
            }

            divider_value = _cyhal_utils_divider_value(hz * oversample_value, 0);
            divided_freq = peri_freq /divider_value;
            diff = (oversampled_freq > divided_freq)
                ? oversampled_freq - divided_freq
                : divided_freq - oversampled_freq;

            if (diff < last_diff)
            {
                last_diff = diff;
                last_ovrsmpl_val = oversample_value;
                last_dvdr_val = divider_value;
                if (0 == diff)
                {
                    break;
                }
            }
        }
        *over_sample_val = last_ovrsmpl_val;
    }
    else
    {
        /* Slave requires such frequency: required_frequency = N / ((0.5 * desired_period) – 20 nsec - tDSI,
        *   N is 3 when "Enable Input Glitch Filter" is false and 4 when true.
        *   tDSI Is external master delay which is assumed to be 16.66 nsec */

        /* Divided by 2 desired period to avoid dividing in required_frequency formula */
        float desired_period_us_divided = 5e5f * (1 / (float)hz);
        uint32_t required_frequency = (uint32_t)(3e6f / (desired_period_us_divided - 36.66f / 1e3f));

        if (required_frequency > peri_freq)
        {
            return CYHAL_SPI_RSLT_CLOCK_ERROR;
        }

        /* Use maximum available clock for slave to make it able to work with any master environment */
        last_dvdr_val = 1;
    }

    result = Cy_SysClk_PeriphSetDivider((cy_en_divider_types_t)obj->clock.block, obj->clock.channel, last_dvdr_val - 1);

    if (CY_RSLT_SUCCESS == result)
    {
        Cy_SysClk_PeriphEnableDivider((cy_en_divider_types_t)obj->clock.block, obj->clock.channel);
    }

    return result;
}

static inline cyhal_spi_event_t _cyhal_spi_convert_interrupt_cause(uint32_t pdl_cause)
{
    static const uint32_t status_map[] =
    {
        (uint32_t)CYHAL_SPI_IRQ_ERROR,         // Default error if unknown value is set
        (uint32_t)CYHAL_SPI_IRQ_DATA_IN_FIFO,  // CY_SCB_SPI_TRANSFER_IN_FIFO_EVENT
        (uint32_t)CYHAL_SPI_IRQ_DONE,          // CY_SCB_SPI_TRANSFER_CMPLT_EVENT
        (uint32_t)CYHAL_SPI_IRQ_ERROR,         // CY_SCB_SPI_TRANSFER_ERR_EVENT
    };
    return (cyhal_spi_event_t)_cyhal_utils_convert_flags(status_map, sizeof(status_map) / sizeof(uint32_t), pdl_cause);
}

static void _cyhal_spi_irq_handler(void)
{
    cyhal_spi_t *obj = (cyhal_spi_t*) _cyhal_scb_get_irq_obj();
    if (NULL == obj)
    {
        return;
    }

    Cy_SCB_SPI_Interrupt(obj->base, &(obj->context));

    if (!obj->is_async)
    {
        return;
    }

    if (0 == (Cy_SCB_SPI_GetTransferStatus(obj->base,  &obj->context) & CY_SCB_SPI_TRANSFER_ACTIVE))
    {
        if (obj->tx_buffer)
        {
            /* Start TX Transfer */
            obj->pending = _CYHAL_SPI_PENDING_TX;
            const uint8_t *buf = obj->tx_buffer;
            obj->tx_buffer = NULL;

            Cy_SCB_SPI_Transfer(obj->base, (uint8_t *)buf, NULL, obj->tx_buffer_size, &obj->context);
        }
        else if (obj->rx_buffer)
        {
            /* Start RX Transfer */
            obj->pending = _CYHAL_SPI_PENDING_RX;
            uint8_t *rx_buf = obj->rx_buffer;
            uint8_t *tx_buf;
            size_t trx_size = obj->rx_buffer_size;

            if (obj->rx_buffer_size > 1)
            {
                 /* In this case we don't have a transmit buffer; we only have a receive buffer. While the PDL
                 * is fine with passing NULL for transmit, we don't get to control what data it is sending in
                 * that case, which we allowed the user to set. To honor the user's request, we reuse the rx
                 * buffer as the tx buffer too. We set all bytes beyond the one we will start filling in with
                 * the user provided 'write_fill'. This means the tx buffer is 1 element smaller than the rx
                 * buffer. As a result, we must therefore transfer 1 less element then we really want to in
                 * this transfer. When this transfer is complete, it will call into this again to receive the
                 * final element.
                 */
                trx_size -= 1; // Transfer everything left except for the last byte

                uint8_t **rx_buffer_p = (uint8_t **) &obj->rx_buffer;

                tx_buf = *rx_buffer_p + 1; // Start at second byte to avoid trying to transmit and receive the same byte
                memset(tx_buf, obj->write_fill, trx_size);

                *rx_buffer_p += trx_size; // Move to 1 byte before end
                obj->rx_buffer_size = 1; // Transfer the last byte on the next interrupt
            }
            else
            {
                tx_buf = &obj->write_fill;

                obj->rx_buffer = NULL;
            }

            Cy_SCB_SPI_Transfer(obj->base, tx_buf, rx_buf, trx_size, &obj->context);
        }
        else
        {
            /* Finish Async Transfer */
            obj->pending = _CYHAL_SPI_PENDING_NONE;
            obj->is_async = false;
            _cyhal_ssel_switch_state(obj, obj->active_ssel, _CYHAL_SPI_SSEL_DEACTIVATE);
        }
    }
}

static void _cyhal_spi_cb_wrapper(uint32_t event)
{
    cyhal_spi_t *obj = (cyhal_spi_t*) _cyhal_scb_get_irq_obj();
    cyhal_spi_irq_event_t anded_events = (cyhal_spi_irq_event_t) (obj->irq_cause & (uint32_t) _cyhal_spi_convert_interrupt_cause(event));

    // Don't call the callback until the final transfer has put everything in the FIFO/completed
    if ((anded_events & (CYHAL_SPI_IRQ_DATA_IN_FIFO | CYHAL_SPI_IRQ_DONE)) && !(obj->rx_buffer == NULL && obj->tx_buffer == NULL))
    {
        return;
    }

    if (anded_events)
    {
        cyhal_spi_event_callback_t callback = (cyhal_spi_event_callback_t) obj->callback_data.callback;
        callback(obj->callback_data.callback_arg, anded_events);
    }
}

static cy_en_scb_spi_sclk_mode_t _cyhal_convert_mode_sclk(cyhal_spi_mode_t mode)
{
    uint8_t sclk_mode = (mode & (CYHAL_SPI_MODE_FLAG_CPOL | CYHAL_SPI_MODE_FLAG_CPHA));

    switch (sclk_mode)
    {
        case CYHAL_SPI_MODE_FLAG_CPOL | CYHAL_SPI_MODE_FLAG_CPHA:
            return (CY_SCB_SPI_CPHA1_CPOL1);
        case CYHAL_SPI_MODE_FLAG_CPOL:
            return (CY_SCB_SPI_CPHA0_CPOL1);
        case CYHAL_SPI_MODE_FLAG_CPHA:
            return (CY_SCB_SPI_CPHA1_CPOL0);
        default:
            return (CY_SCB_SPI_CPHA0_CPOL0);
    }
}

static inline bool _is_cyhal_mode_msb(cyhal_spi_mode_t mode)
{
    return ((mode & CYHAL_SPI_MODE_FLAG_LSB) != CYHAL_SPI_MODE_FLAG_LSB);
}

static bool _cyhal_spi_pm_callback_instance(void *obj_ptr, cyhal_syspm_callback_state_t state, cy_en_syspm_callback_mode_t pdl_mode)
{
    cyhal_spi_t *obj = (cyhal_spi_t *)obj_ptr;
    bool allow = true;
    cy_stc_syspm_callback_params_t spi_callback_params = {
        .base = (void *) (obj->base),
        .context = (void *) &(obj->context)
    };

    if (CYHAL_SYSPM_CB_CPU_DEEPSLEEP == state)
        allow = (CY_SYSPM_SUCCESS == Cy_SCB_SPI_DeepSleepCallback(&spi_callback_params, pdl_mode));
#if defined(COMPONENT_CAT1A) || defined(COMPONENT_CAT1B)
    else if (CYHAL_SYSPM_CB_SYSTEM_HIBERNATE == state)
        allow = (CY_SYSPM_SUCCESS == Cy_SCB_SPI_HibernateCallback(&spi_callback_params, pdl_mode));
#endif

    return allow;
}

static cy_rslt_t _cyhal_spi_get_ssel_map_idx(cyhal_spi_t *obj, cyhal_gpio_t ssel,
                        const cyhal_resource_pin_mapping_t **ssel_map, uint8_t *idx)
{
    #ifdef CY_IP_M0S8SCB
    /* SSEL0 is only available for Slave on M0S8SCB */
    static const cyhal_resource_pin_mapping_t *ssel_s_pin_maps[] = { cyhal_pin_map_scb_spi_s_select0 };
    static const size_t ssel_s_pin_maps_sizes_bytes[] = { sizeof(cyhal_pin_map_scb_spi_s_select0) };
    #else
    static const cyhal_resource_pin_mapping_t *ssel_s_pin_maps[] = {
        cyhal_pin_map_scb_spi_s_select0, cyhal_pin_map_scb_spi_s_select1,
        cyhal_pin_map_scb_spi_s_select2, cyhal_pin_map_scb_spi_s_select3 };
    static const size_t ssel_s_pin_maps_sizes_bytes[] = {
        sizeof(cyhal_pin_map_scb_spi_s_select0), sizeof(cyhal_pin_map_scb_spi_s_select1),
        sizeof(cyhal_pin_map_scb_spi_s_select2), sizeof(cyhal_pin_map_scb_spi_s_select3) };
    #endif /* M0S8 version of SCB or other */
    for (uint8_t i = 0; i < sizeof(ssel_s_pin_maps) / sizeof(ssel_s_pin_maps[0]); i++)
    {
        *ssel_map = _cyhal_scb_find_map(ssel, ssel_s_pin_maps[i],
                        ssel_s_pin_maps_sizes_bytes[i] / sizeof(cyhal_resource_pin_mapping_t), &(obj->resource));
        if (NULL != *ssel_map)
        {
            *idx = i;
            return CY_RSLT_SUCCESS;
        }
    }
    return CYHAL_SPI_RSLT_ERR_CANNOT_CONFIG_SSEL;
}

cy_rslt_t cyhal_spi_init(cyhal_spi_t *obj, cyhal_gpio_t mosi, cyhal_gpio_t miso, cyhal_gpio_t sclk, cyhal_gpio_t ssel,
                        const cyhal_clock_t *clk, uint8_t bits, cyhal_spi_mode_t mode, bool is_slave)
{
    CY_ASSERT(NULL != obj);
    memset(obj, 0, sizeof(cyhal_spi_t));

    cy_rslt_t result = CY_RSLT_SUCCESS;
    uint8_t ovr_sample_val = _CYHAL_SPI_OVERSAMPLE_MIN;
    obj->pending = _CYHAL_SPI_PENDING_NONE;

    // Explicitly marked not allocated resources as invalid to prevent freeing them.
    obj->resource.type = CYHAL_RSC_INVALID;
    obj->pin_miso = NC;
    obj->pin_mosi = NC;
    obj->pin_sclk = NC;

    for (uint8_t i = 0; i < _CYHAL_SPI_SSEL_NUM; i++)
    {
        obj->pin_ssel[i] = NC;
        obj->ssel_pol[i] = CY_SCB_SPI_ACTIVE_LOW;
    }

    obj->write_fill = (uint8_t) CY_SCB_SPI_DEFAULT_TX;
    obj->active_ssel = 0;

    /* Validate pins configuration. Mandatory pins:*/
    /* Master mode: MOSI pin used, MISO unused:     SCLK, SSEL are optional */
    /* Master mode: MISO pin used, MOSI unused:     SCLK is mandatory, MOSI, SSEL are optional */
    /* Slave  mode: MOSI or MISO are used:          SCLK and SSEL are mandatory */

    /* Slave */
    if (is_slave)
    {
        if ((NC == sclk) || (NC == ssel) || ((NC == mosi) && (NC == miso)))
        {
            return CYHAL_SPI_RSLT_PIN_CONFIG_NOT_SUPPORTED;
        }
    }
    /* Master */
    else
    {
        if ((NC != miso && NC == sclk) || (NC == mosi && NC == miso))
        {
            return CYHAL_SPI_RSLT_PIN_CONFIG_NOT_SUPPORTED;
        }
    }

    obj->is_slave = is_slave;

    /* Get pin configurations */
    const cyhal_resource_pin_mapping_t *mosi_map = NULL;
    const cyhal_resource_pin_mapping_t *miso_map = NULL;
    const cyhal_resource_pin_mapping_t *sclk_map = NULL;
    const cyhal_resource_pin_mapping_t *ssel_map = NULL;
    uint8_t active_ssel = 0;
    if (is_slave)
    {
        mosi_map = _CYHAL_SCB_FIND_MAP(mosi, cyhal_pin_map_scb_spi_s_mosi);
        miso_map = _CYHAL_SCB_FIND_MAP(miso, cyhal_pin_map_scb_spi_s_miso);
        sclk_map = _CYHAL_SCB_FIND_MAP(sclk, cyhal_pin_map_scb_spi_s_clk);
        result = _cyhal_spi_get_ssel_map_idx(obj, ssel, &ssel_map, &active_ssel);
    }
    else
    {
        mosi_map = _CYHAL_SCB_FIND_MAP(mosi, cyhal_pin_map_scb_spi_m_mosi);
        miso_map = _CYHAL_SCB_FIND_MAP(miso, cyhal_pin_map_scb_spi_m_miso);
        sclk_map = _CYHAL_SCB_FIND_MAP(sclk, cyhal_pin_map_scb_spi_m_clk);
        /* No need to find maps for ssel pins, as GPIO used */
    }

    const cyhal_resource_inst_t *spi_inst = (NC != mosi)
        ? (mosi_map != NULL ? mosi_map->inst : NULL)
        : (miso_map != NULL ? miso_map->inst : NULL);

    /* Validate pins mapping */
    if (NULL == spi_inst ||
        ((NC != mosi) && ((NULL == mosi_map) || !_cyhal_utils_resources_equal(spi_inst, mosi_map->inst))) ||
        ((NC != miso) && ((NULL == miso_map) || !_cyhal_utils_resources_equal(spi_inst, miso_map->inst))) ||
        ((NC != sclk) && ((NULL == sclk_map) || !_cyhal_utils_resources_equal(spi_inst, sclk_map->inst))) ||
        ((is_slave) && ((NC != ssel) && ((NULL == ssel_map) || !_cyhal_utils_resources_equal(spi_inst, ssel_map->inst)))))
    {
        return CYHAL_SPI_RSLT_ERR_INVALID_PIN;
    }

    if (CY_RSLT_SUCCESS != (result = cyhal_hwmgr_reserve(spi_inst)))
    {
        return result;
    }

    obj->resource = *spi_inst;
    obj->base = _CYHAL_SCB_BASE_ADDRESSES[obj->resource.block_num];

    // reserve the MOSI pin
    if ((result == CY_RSLT_SUCCESS) && (NC != mosi))
    {
        result = _cyhal_utils_reserve_and_connect(mosi, mosi_map);
        if (result == CY_RSLT_SUCCESS)
        {
            obj->pin_mosi = mosi;
        }
    }

    // reserve the MISO pin
    if ((result == CY_RSLT_SUCCESS) && (NC != miso))
    {
        result = _cyhal_utils_reserve_and_connect(miso, miso_map);
        if (result == CY_RSLT_SUCCESS)
        {
            obj->pin_miso = miso;
        }
    }

    // reserve the SCLK pin
    if (result == CY_RSLT_SUCCESS && (NC != sclk))
    {
        result = _cyhal_utils_reserve_and_connect(sclk, sclk_map);
        if (result == CY_RSLT_SUCCESS)
        {
            obj->pin_sclk = sclk;
        }
    }

    // reserve and configure the SSEL pin
    if ((result == CY_RSLT_SUCCESS) && (NC != ssel))
    {
        result = cyhal_spi_slave_select_config(obj, ssel, CYHAL_SPI_SSEL_ACTIVE_LOW);
    }

    if (result == CY_RSLT_SUCCESS)
    {
        if (clk == NULL)
        {
            result = cyhal_clock_allocate(&(obj->clock), CYHAL_CLOCK_BLOCK_PERIPHERAL_16BIT);
            obj->alloc_clock = true;
        }
        else
        {

            obj->clock = *clk;
            obj->alloc_clock = false;
            _cyhal_utils_update_clock_format(&(obj->clock));

            /* Per CDT 315848 and 002-20730 Rev. *E:
             * For SPI, an integer clock divider must be used for both master and slave. */
            if ((obj->clock.block == CYHAL_CLOCK_BLOCK_PERIPHERAL_16_5BIT) || (obj->clock.block == CYHAL_CLOCK_BLOCK_PERIPHERAL_24_5BIT))
            {
                result = CYHAL_SPI_RSLT_CLOCK_NOT_SUPPORTED;
            }
        }
    }
    if (result == CY_RSLT_SUCCESS)
    {
        result = (cy_rslt_t)Cy_SysClk_PeriphAssignDivider(
                _cyhal_scb_get_clock_index(obj->resource.block_num),
                (cy_en_divider_types_t)obj->clock.block, obj->clock.channel);

        if (result == CY_RSLT_SUCCESS)
        {
            result = _cyhal_spi_int_frequency(obj, _CYHAL_SPI_DEFAULT_SPEED, &ovr_sample_val);
        }
    }

    if (result == CY_RSLT_SUCCESS)
    {
        _cyhal_scb_update_instance_data(obj->resource.block_num, (void*)obj, &_cyhal_spi_pm_callback_instance);
        cy_stc_scb_spi_config_t config_structure = _cyhal_spi_default_config;
        config_structure.spiMode = is_slave == 0
            ? CY_SCB_SPI_MASTER
            : CY_SCB_SPI_SLAVE;
        obj->msb_first = _is_cyhal_mode_msb(mode);
        config_structure.enableMsbFirst = obj->msb_first;
        obj->clk_mode = _cyhal_convert_mode_sclk(mode);
        config_structure.sclkMode = obj->clk_mode;
        config_structure.rxDataWidth = bits;
        config_structure.txDataWidth = bits;
        config_structure.oversample = ovr_sample_val;
        obj->data_bits = bits;
        obj->mode = (uint8_t) mode;
        obj->oversample_value = ovr_sample_val;
        Cy_SCB_SPI_Init(obj->base, &config_structure, &(obj->context));

        /* Activating specified by user ssel after init */
        if (NC != ssel)
        {
            result = cyhal_spi_select_active_ssel(obj, ssel);
        }

        obj->callback_data.callback = NULL;
        obj->callback_data.callback_arg = NULL;
        obj->irq_cause = 0;

        cy_stc_sysint_t irqCfg = { _CYHAL_SCB_IRQ_N[obj->resource.block_num], CYHAL_ISR_PRIORITY_DEFAULT };
        Cy_SysInt_Init(&irqCfg, _cyhal_spi_irq_handler);
        NVIC_EnableIRQ(_CYHAL_SCB_IRQ_N[obj->resource.block_num]);

        Cy_SCB_SPI_Enable(obj->base);
    }
    else
    {
        cyhal_spi_free(obj);
    }
    return result;
}

void cyhal_spi_free(cyhal_spi_t *obj)
{
    if (NULL != obj->base)
    {
        _cyhal_scb_update_instance_data(obj->resource.block_num, NULL, NULL);
        Cy_SCB_SPI_Disable(obj->base, NULL);
        Cy_SCB_SPI_DeInit(obj->base);
        obj->base = NULL;
    }

    if (obj->resource.type != CYHAL_RSC_INVALID)
    {
        IRQn_Type irqn = _CYHAL_SCB_IRQ_N[obj->resource.block_num];
        NVIC_DisableIRQ(irqn);

        cyhal_hwmgr_free(&(obj->resource));
        obj->resource.type = CYHAL_RSC_INVALID;
    }

    _cyhal_utils_release_if_used(&(obj->pin_miso));
    _cyhal_utils_release_if_used(&(obj->pin_mosi));
    _cyhal_utils_release_if_used(&(obj->pin_sclk));
    for (uint8_t i = 0; i < _CYHAL_SPI_SSEL_NUM; i++)
    {
        _cyhal_utils_release_if_used(&(obj->pin_ssel[i]));
    }

    if (obj->alloc_clock)
    {
       cyhal_clock_free(&(obj->clock));
       obj->alloc_clock = false;
    }
}

static void _cyhal_ssel_switch_state(cyhal_spi_t *obj, uint8_t ssel_idx, bool ssel_activate)
{
    if ((!obj->is_slave) && (CYHAL_NC_PIN_VALUE != obj->pin_ssel[ssel_idx]))
    {
        /* Situations described:
        *   ssel_activate = true (need to set SSEL into active state)
        *       CY_SCB_SPI_ACTIVE_LOW - writing 0 to ssel pin
        *       CY_SCB_SPI_ACTIVE_HIGH - writing 1 to ssel pin
        *   ssel_activate = false (need to set SSEL into inactive state)
        *       CY_SCB_SPI_ACTIVE_LOW - writing 1 to ssel pin
        *       CY_SCB_SPI_ACTIVE_HIGH - writing 0 to ssel pin */
        bool ssel_state = (CY_SCB_SPI_ACTIVE_LOW == obj->ssel_pol[ssel_idx]) ? !ssel_activate : ssel_activate;
        cyhal_gpio_write(obj->pin_ssel[ssel_idx], ssel_state);
    }
}

cy_rslt_t cyhal_spi_set_frequency(cyhal_spi_t *obj, uint32_t hz)
{
    cy_rslt_t result = CY_RSLT_SUCCESS;
    uint8_t   ovr_sample_val;

    if (NULL == obj)
    {
        return CYHAL_SPI_RSLT_BAD_ARGUMENT;
    }

    Cy_SCB_SPI_Disable(obj->base, &obj->context);
    result = _cyhal_spi_int_frequency(obj, hz, &ovr_sample_val);

    /* No need to reconfigure slave since oversample value, that was changed in _cyhal_spi_int_frequency, in slave is ignored */
    if ((CY_RSLT_SUCCESS == result) && !obj->is_slave && (obj->oversample_value != ovr_sample_val))
    {
       cy_stc_scb_spi_config_t config_structure = _cyhal_spi_default_config;
       Cy_SCB_SPI_DeInit(obj->base);
       config_structure.spiMode = obj->is_slave == false
           ? CY_SCB_SPI_MASTER
           : CY_SCB_SPI_SLAVE;
       config_structure.enableMsbFirst = obj->msb_first;
       config_structure.sclkMode = obj->clk_mode;
       config_structure.rxDataWidth = obj->data_bits;
       config_structure.txDataWidth = obj->data_bits;
       config_structure.oversample = ovr_sample_val;
       obj->oversample_value = ovr_sample_val;
       Cy_SCB_SPI_Init(obj->base, &config_structure, &(obj->context));
    }

    Cy_SCB_SPI_Enable(obj->base);

    return result;
}

cy_rslt_t cyhal_spi_select_active_ssel(cyhal_spi_t *obj, cyhal_gpio_t ssel)
{
    CY_ASSERT(NULL != obj);
    CY_ASSERT(NULL != obj->base);
    if ((NC != ssel) && (_CYHAL_SPI_PENDING_NONE == obj->pending))
    {
        for (uint8_t i = 0; i < _CYHAL_SPI_SSEL_NUM; i++)
        {
            if(obj->pin_ssel[i] == ssel)
            {
                Cy_SCB_SPI_SetActiveSlaveSelect(obj->base, (cy_en_scb_spi_slave_select_t)i);
                obj->active_ssel = i;
                return CY_RSLT_SUCCESS;
            }
        }
    }
    return CYHAL_SPI_RSLT_ERR_CANNOT_SWITCH_SSEL;
}

static inline cy_en_scb_spi_polarity_t _cyhal_spi_pol_from_hal_to_pdl(cyhal_spi_ssel_polarity_t hal_polarity)
{
    return (hal_polarity == CYHAL_SPI_SSEL_ACTIVE_HIGH) ? CY_SCB_SPI_ACTIVE_HIGH : CY_SCB_SPI_ACTIVE_LOW;
}

cy_rslt_t cyhal_spi_slave_select_config(cyhal_spi_t *obj, cyhal_gpio_t ssel, cyhal_spi_ssel_polarity_t polarity)
{
    CY_ASSERT(NULL != obj);
    cy_rslt_t result = CYHAL_SPI_RSLT_ERR_CANNOT_CONFIG_SSEL;
    uint8_t found_idx = 0;
    bool configuring_existing = false;
    if ((NC != ssel) && (_CYHAL_SPI_PENDING_NONE == obj->pending))
    {
        for (uint8_t i = 0; i < _CYHAL_SPI_SSEL_NUM; i++)
        {
            if ((configuring_existing = (ssel == obj->pin_ssel[i])))
            {
                result = CY_RSLT_SUCCESS;
                found_idx = i;
                break;
            }
            if (!obj->is_slave)
            {
                /* Looking for first available ssel slot */
                if ((NC == obj->pin_ssel[i]))
                {
                    result = cyhal_gpio_init(ssel, CYHAL_GPIO_DIR_OUTPUT, CYHAL_GPIO_DRIVE_STRONG,
                                (polarity == CYHAL_SPI_SSEL_ACTIVE_LOW) ? true : false);
                    found_idx = i;
                    break;
                }
            }
        }
        if (!configuring_existing && (obj->is_slave))
        {
            const cyhal_resource_pin_mapping_t *ssel_map = NULL;
            if (CY_RSLT_SUCCESS == _cyhal_spi_get_ssel_map_idx(obj, ssel, &ssel_map, &found_idx))
            {
                /* Either mosi or miso should present. Will take one of them as instance SCB instance index source */
                const cyhal_resource_pin_mapping_t *data_pin_map = (NC != obj->pin_mosi) ?
                    _CYHAL_SCB_FIND_MAP_BLOCK(obj->pin_mosi, cyhal_pin_map_scb_spi_s_mosi, &(obj->resource)) :
                    _CYHAL_SCB_FIND_MAP_BLOCK(obj->pin_miso, cyhal_pin_map_scb_spi_s_miso, &(obj->resource));

                if ((NULL != ssel_map) && (NC == obj->pin_ssel[found_idx]) &&
                    _cyhal_utils_resources_equal(data_pin_map->inst, ssel_map->inst))
                {
                    result = _cyhal_utils_reserve_and_connect(ssel, ssel_map);
                }
            }
        }
        if (CY_RSLT_SUCCESS == result)
        {
            if (!configuring_existing)
                obj->pin_ssel[found_idx] = ssel;
            obj->ssel_pol[found_idx] = _cyhal_spi_pol_from_hal_to_pdl(polarity);

            /* Immediatelly apply updated slave select polarity */
            Cy_SCB_SPI_SetActiveSlaveSelectPolarity(obj->base, (cy_en_scb_spi_slave_select_t)found_idx, obj->ssel_pol[found_idx]);
            if (!obj->is_slave)
                _cyhal_ssel_switch_state(obj, found_idx, _CYHAL_SPI_SSEL_DEACTIVATE);
        }
    }
    return result;
}

cy_rslt_t cyhal_spi_recv(cyhal_spi_t *obj, uint32_t *value)
{
    if (NULL == obj)
        return CYHAL_SPI_RSLT_BAD_ARGUMENT;

    if (_cyhal_scb_pm_transition_pending())
        return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;

    uint32_t read_value = CY_SCB_SPI_RX_NO_DATA;
    const uint32_t fill_in = 0x0000ffffUL; /* PDL Fill in value */
    uint32_t count = 0;

    if ((obj->is_slave) && (CYHAL_NC_PIN_VALUE == obj->pin_mosi))
    {
        return CYHAL_SPI_RSLT_INVALID_PIN_API_NOT_SUPPORTED;
    }

    if ((!obj->is_slave) && (CYHAL_NC_PIN_VALUE == obj->pin_miso))
    {
        return CYHAL_SPI_RSLT_INVALID_PIN_API_NOT_SUPPORTED;
    }

    if (!obj->is_slave)
    {
        _cyhal_ssel_switch_state(obj, obj->active_ssel, _CYHAL_SPI_SSEL_ACTIVATE);

        /* Clear FIFOs */
        Cy_SCB_SPI_ClearTxFifo(obj->base);
        Cy_SCB_SPI_ClearRxFifo(obj->base);

        while (count == 0)
        {
            count = Cy_SCB_SPI_Write(obj->base, fill_in);
        }

        while (Cy_SCB_SPI_IsTxComplete(obj->base) == false) { }
        while (Cy_SCB_SPI_GetNumInRxFifo(obj->base) == 0) { } /* Wait for RX FIFO not empty */
        _cyhal_ssel_switch_state(obj, obj->active_ssel, _CYHAL_SPI_SSEL_DEACTIVATE);
    }

    while (read_value == CY_SCB_SPI_RX_NO_DATA)
    {
        read_value = Cy_SCB_SPI_Read(obj->base);
    }
    *value = read_value;
    return CY_RSLT_SUCCESS;
}

cy_rslt_t cyhal_spi_send(cyhal_spi_t *obj, uint32_t value)
{
    if (NULL == obj)
        return CYHAL_SPI_RSLT_BAD_ARGUMENT;

    if (_cyhal_scb_pm_transition_pending())
        return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;

    uint32_t count = 0;
    cy_rslt_t result = CY_RSLT_SUCCESS;

    if ((obj->is_slave) && (CYHAL_NC_PIN_VALUE == obj->pin_miso))
    {
        return CYHAL_SPI_RSLT_INVALID_PIN_API_NOT_SUPPORTED;
    }

    if ((!obj->is_slave) && (CYHAL_NC_PIN_VALUE == obj->pin_mosi))
    {
        return CYHAL_SPI_RSLT_INVALID_PIN_API_NOT_SUPPORTED;
    }

    if (!obj->is_slave)
    {
        _cyhal_ssel_switch_state(obj, obj->active_ssel, _CYHAL_SPI_SSEL_ACTIVATE);

        /* Clear FIFOs */
        Cy_SCB_SPI_ClearTxFifo(obj->base);
        Cy_SCB_SPI_ClearRxFifo(obj->base);
    }

    while (count == 0)
    {
        count = Cy_SCB_SPI_Write(obj->base, value);
    }

    if (!obj->is_slave)
    {
        while (Cy_SCB_SPI_IsTxComplete(obj->base) == false) { }
        while (Cy_SCB_SPI_GetNumInRxFifo(obj->base) == 0) { } /* Wait for RX FIFO not empty */
        _cyhal_ssel_switch_state(obj, obj->active_ssel, _CYHAL_SPI_SSEL_DEACTIVATE);
        (void)Cy_SCB_SPI_Read(obj->base);
    }

    return result;
}

cy_rslt_t cyhal_spi_transfer(cyhal_spi_t *obj, const uint8_t *tx, size_t tx_length, uint8_t *rx, size_t rx_length, uint8_t write_fill)
{
    if (NULL == obj)
        return CYHAL_SPI_RSLT_BAD_ARGUMENT;

    if (_cyhal_scb_pm_transition_pending())
        return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;

    obj->write_fill = write_fill;
    cy_rslt_t rslt = cyhal_spi_transfer_async(obj, tx, tx_length, rx, rx_length);
    if (rslt == CY_RSLT_SUCCESS)
    {
        while (obj->pending != _CYHAL_SPI_PENDING_NONE) { } /* Wait for async transfer to complete */
    }
    obj->write_fill = (uint8_t) CY_SCB_SPI_DEFAULT_TX;
    return rslt;
}

cy_rslt_t cyhal_spi_transfer_async(cyhal_spi_t *obj, const uint8_t *tx, size_t tx_length, uint8_t *rx, size_t rx_length)
{
    if (NULL == obj)
        return CYHAL_SPI_RSLT_BAD_ARGUMENT;

    if (_cyhal_scb_pm_transition_pending())
        return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;

    cy_en_scb_spi_status_t spi_status;

    if ((CYHAL_NC_PIN_VALUE == obj->pin_mosi) || (CYHAL_NC_PIN_VALUE == obj->pin_miso))
    {
        return CYHAL_SPI_RSLT_INVALID_PIN_API_NOT_SUPPORTED;
    }

    _cyhal_ssel_switch_state(obj, obj->active_ssel, _CYHAL_SPI_SSEL_ACTIVATE);
    obj->is_async = true;

    /* Setup transfer */
    if (tx_length > rx_length)
    {
        if (rx_length > 0)
        {
            /* I) write + read, II) write only */
            obj->pending = _CYHAL_SPI_PENDING_TX_RX;
            obj->rx_buffer = NULL;

            obj->tx_buffer = tx + (obj->data_bits <= 8 ? rx_length : (rx_length * 2));
            obj->tx_buffer_size = tx_length - rx_length;

            tx_length = rx_length; // Use tx_length to store entire transfer length
        }
        else
        {
            /*  I) write only */
            obj->pending = _CYHAL_SPI_PENDING_TX;
            obj->rx_buffer = NULL;
            obj->tx_buffer = NULL;

            rx = NULL;
        }
    }
    else if (rx_length > tx_length)
    {
        if (tx_length > 0)
        {
            /*  I) write + read, II) read only */
            obj->pending = _CYHAL_SPI_PENDING_TX_RX;
            obj->tx_buffer = NULL;

            obj->rx_buffer = rx + (obj->data_bits <= 8 ? tx_length : (tx_length * 2));
            obj->rx_buffer_size = rx_length - tx_length;
        }
        else
        {
            /*  I) read only. */
            obj->pending = _CYHAL_SPI_PENDING_RX;
            obj->tx_buffer = NULL;

            obj->rx_buffer = rx_length > 1 ? rx + 1 : NULL;
            obj->rx_buffer_size = rx_length - 1;
            tx = &obj->write_fill;
            tx_length = 1;
        }
    }
    else
    {
        /* RX and TX of the same size: I) write + read. */
        obj->pending = _CYHAL_SPI_PENDING_TX_RX;
        obj->rx_buffer = NULL;
        obj->tx_buffer = NULL;
    }
    spi_status = Cy_SCB_SPI_Transfer(obj->base, (void *)tx, rx, tx_length, &obj->context);
    return spi_status == CY_SCB_SPI_SUCCESS
        ? CY_RSLT_SUCCESS
        : CYHAL_SPI_RSLT_TRANSFER_ERROR;
}

bool cyhal_spi_is_busy(cyhal_spi_t *obj)
{
    return Cy_SCB_SPI_IsBusBusy(obj->base) || (_CYHAL_SPI_PENDING_NONE != obj->pending);
}

cy_rslt_t cyhal_spi_abort_async(cyhal_spi_t *obj)
{
    if (NULL == obj)
    {
        return CYHAL_SPI_RSLT_BAD_ARGUMENT;
    }

    Cy_SCB_SPI_AbortTransfer(obj->base, &(obj->context));
    obj->pending = _CYHAL_SPI_PENDING_NONE;
    return CY_RSLT_SUCCESS;
}

void cyhal_spi_register_callback(cyhal_spi_t *obj, cyhal_spi_event_callback_t callback, void *callback_arg)
{
    uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
    obj->callback_data.callback = (cy_israddress) callback;
    obj->callback_data.callback_arg = callback_arg;
    cyhal_system_critical_section_exit(savedIntrStatus);
    Cy_SCB_SPI_RegisterCallback(obj->base, _cyhal_spi_cb_wrapper, &(obj->context));

    obj->irq_cause = 0;
}

void cyhal_spi_enable_event(cyhal_spi_t *obj, cyhal_spi_event_t event, uint8_t intr_priority, bool enable)
{
    if (enable)
    {
        obj->irq_cause |= event;
    }
    else
    {
        obj->irq_cause &= ~event;
    }

    IRQn_Type irqn = _CYHAL_SCB_IRQ_N[obj->resource.block_num];
    NVIC_SetPriority(irqn, intr_priority);
}

cy_rslt_t cyhal_spi_set_fifo_level(cyhal_spi_t *obj, cyhal_spi_fifo_type_t type, uint16_t level)
{
    return _cyhal_scb_set_fifo_level(obj->base, (cyhal_scb_fifo_type_t)type, level);
}

cy_rslt_t cyhal_spi_enable_output(cyhal_spi_t *obj, cyhal_spi_output_t output, cyhal_source_t *source)
{
    return _cyhal_scb_enable_output(obj->base, obj->resource, (cyhal_scb_output_t)output, source);
}

cy_rslt_t cyhal_spi_disable_output(cyhal_spi_t *obj, cyhal_spi_output_t output)
{
    return _cyhal_scb_disable_output(obj->base, obj->resource, (cyhal_scb_output_t)output);
}

#if defined(__cplusplus)
}
#endif

#endif /* CY_IP_MXSCB */