/***************************************************************************//** * \file cyhal_qspi.c * * Description: * Provides a high level interface for interacting with the Cypress QSPI (SMIF). * 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 <string.h> #include <math.h> #include <stdlib.h> #include "cy_smif.h" #include "cyhal_utils.h" #include "cyhal_qspi.h" #include "cyhal_hwmgr.h" #include "cyhal_gpio.h" #include "cyhal_interconnect.h" #include "cyhal_system_impl.h" #include "cyhal_syspm.h" #include "cyhal_clock.h" #ifdef CY_IP_MXSMIF #if defined(__cplusplus) extern "C" { #endif /* __cplusplus */ /******************************************************************************* * Internal *******************************************************************************/ /* in microseconds, timeout for all blocking functions */ #define _CYHAL_QSPI_TIMEOUT_10_MS (10000UL) #define _CYHAL_QSPI_MAX_DATA_PINS 8 #define _CYHAL_QSPI_MAX_RX_COUNT (65536UL) #define _CYHAL_QSPI_DESELECT_DELAY (7UL) #if (defined(SMIF_CHIP_TOP_DATA8_PRESENT) && (SMIF_CHIP_TOP_DATA8_PRESENT)) || \ (defined(SMIF0_CHIP_TOP_DATA8_PRESENT) && (SMIF0_CHIP_TOP_DATA8_PRESENT)) #define _CYHAL_QSPI_DATA8_PRESENT 1 #else #define _CYHAL_QSPI_DATA8_PRESENT 0 #endif #if (SMIF_CHIP_TOP_SPI_SEL_NR > 1) #define _CYHAL_QSPI_SEL1 1 #else #define _CYHAL_QSPI_SEL1 0 #endif #if (SMIF_CHIP_TOP_SPI_SEL_NR > 2) #define _CYHAL_QSPI_SEL2 1 #else #define _CYHAL_QSPI_SEL2 0 #endif #if (SMIF_CHIP_TOP_SPI_SEL_NR > 3) #define _CYHAL_QSPI_SEL3 1 #else #define _CYHAL_QSPI_SEL3 0 #endif static cyhal_qspi_t *_cyhal_qspi_config_structs[CY_IP_MXSMIF_INSTANCES]; /* Default QSPI configuration */ static const cy_stc_smif_config_t _cyhal_qspi_default_config = { .mode = (uint32_t)CY_SMIF_NORMAL, .deselectDelay = _CYHAL_QSPI_DESELECT_DELAY, .rxClockSel = (uint32_t)CY_SMIF_SEL_INV_INTERNAL_CLK, .blockEvent = (uint32_t)CY_SMIF_BUS_ERROR, }; /* List of available QSPI instances */ static SMIF_Type *const _cyhal_qspi_base_addresses[CY_IP_MXSMIF_INSTANCES] = { #ifdef SMIF0 SMIF0, #endif /* ifdef SMIF0 */ }; /* List of available QSPI interrupt sources */ static const IRQn_Type _CYHAL_QSPI_IRQ_N[CY_IP_MXSMIF_INSTANCES] = { #ifdef SMIF0 smif_interrupt_IRQn, #endif /* ifdef SMIF0 */ }; static inline uint8_t _cyhal_qspi_get_block_from_irqn(IRQn_Type irqn) { switch (irqn) { #if (CY_CPU_CORTEX_M4) #ifdef SMIF0 case smif_interrupt_IRQn: return 0; #endif /* ifdef SMIF0 */ #endif /* (CY_CPU_CORTEX_M4) */ default: CY_ASSERT(false); // Should never be called with a non-SMIF IRQn return 0; } } static cyhal_qspi_t *_cyhal_qspi_get_irq_obj(void) { IRQn_Type irqn = _CYHAL_UTILS_GET_CURRENT_IRQN(); uint8_t block = _cyhal_qspi_get_block_from_irqn(irqn); return _cyhal_qspi_config_structs[block]; } static void _cyhal_qspi_set_pins_frozen(cyhal_qspi_t* obj, bool freeze) { GPIO_PRT_Type* port; uint8_t pin; cyhal_gpio_t gpio; for(size_t i = 0; i < sizeof(obj->pin_ios)/sizeof(obj->pin_ios[0]); ++i) { gpio = obj->pin_ios[i]; if(NC != gpio) { port = CYHAL_GET_PORTADDR(gpio); pin = (uint8_t)CYHAL_GET_PIN(gpio); if(freeze) { obj->saved_io_hsiom[i] = Cy_GPIO_GetHSIOM(port, pin); Cy_GPIO_Clr(port, pin); Cy_GPIO_SetHSIOM(port, pin, HSIOM_SEL_GPIO); } else { Cy_GPIO_SetHSIOM(port, pin, obj->saved_io_hsiom[i]); } } } gpio = obj->pin_sclk; if(NC != gpio) { port = CYHAL_GET_PORTADDR(gpio); pin = (uint8_t)CYHAL_GET_PIN(gpio); if(freeze) { obj->saved_sclk_hsiom = Cy_GPIO_GetHSIOM(port, pin); Cy_GPIO_Clr(port, pin); Cy_GPIO_SetHSIOM(port, pin, HSIOM_SEL_GPIO); } else { Cy_GPIO_SetHSIOM(port, pin, obj->saved_sclk_hsiom); } } for(size_t i = 0; i < sizeof(obj->pin_ssel)/sizeof(obj->pin_ssel[0]); ++i) { gpio = obj->pin_ssel[i]; if(NC != gpio) { port = CYHAL_GET_PORTADDR(gpio); pin = (uint8_t)CYHAL_GET_PIN(gpio); if(freeze) { obj->saved_ssel_hsiom[i] = Cy_GPIO_GetHSIOM(port, pin); Cy_GPIO_Set(port, pin); // The SMIF IP requires SSEL to be active low Cy_GPIO_SetHSIOM(port, pin, HSIOM_SEL_GPIO); } else { Cy_GPIO_SetHSIOM(port, pin, obj->saved_ssel_hsiom[i]); } } } } static bool _cyhal_qspi_pm_callback(cyhal_syspm_callback_state_t state, cyhal_syspm_callback_mode_t mode, void* callback_arg) { CY_UNUSED_PARAMETER(state); cyhal_qspi_t *obj = (cyhal_qspi_t *)callback_arg; bool allow = true; switch(mode) { case CYHAL_SYSPM_CHECK_READY: allow &= obj->context.txBufferCounter == 0; allow &= obj->context.rxBufferCounter == 0; allow &= Cy_SMIF_GetRxFifoStatus(obj->base) == 0; allow &= Cy_SMIF_GetTxFifoStatus(obj->base) == 0; if (allow) { obj->pm_transition_pending = true; } break; case CYHAL_SYSPM_BEFORE_TRANSITION: _cyhal_qspi_set_pins_frozen(obj, true); break; case CYHAL_SYSPM_AFTER_TRANSITION: _cyhal_qspi_set_pins_frozen(obj, false); obj->pm_transition_pending = false; break; case CYHAL_SYSPM_CHECK_FAIL: obj->pm_transition_pending = false; break; default: break; } return allow; } /******************************************************************************* * Dispatcher Interrupt Service Routine *******************************************************************************/ static void _cyhal_qspi_cb_wrapper(uint32_t event) { cyhal_qspi_event_t hal_event = CYHAL_QSPI_EVENT_NONE; if (event == CY_SMIF_SEND_CMPLT) hal_event = CYHAL_QSPI_IRQ_TRANSMIT_DONE; else if (event == CY_SMIF_REC_CMPLT) hal_event = CYHAL_QSPI_IRQ_RECEIVE_DONE; cyhal_qspi_t *obj = _cyhal_qspi_get_irq_obj(); if ((obj->irq_cause & (uint32_t)hal_event) > 0) // Make sure a user requested event is set before calling { cyhal_qspi_event_callback_t callback = (cyhal_qspi_event_callback_t) obj->callback_data.callback; callback(obj->callback_data.callback_arg, hal_event); } } /******************************************************************************* * (Internal) Interrupt Service Routines *******************************************************************************/ /* Interrupt call, needed for SMIF Async operations */ static void _cyhal_qspi_irq_handler(void) { cyhal_qspi_t *obj = _cyhal_qspi_get_irq_obj(); Cy_SMIF_Interrupt(obj->base, &(obj->context)); } /******************************************************************************* * (Internal) QSPI Pin Related Functions *******************************************************************************/ /* Check if pin valid as resource and reserve it */ static cy_rslt_t _cyhal_qspi_check_pin_and_reserve(cyhal_gpio_t pin, const cyhal_resource_pin_mapping_t *mapping) { // Mbed calls qspi_init multiple times without calling qspi_free to update the QSPI frequency/mode. // As a result, we can't worry about resource reservation if running through mbed. #ifndef __MBED__ cy_rslt_t result = _cyhal_utils_reserve_and_connect(pin, mapping); #else cy_rslt_t result = cyhal_connect_pin(mapping); #endif return result; } /******************************************************************************* * (Internal) QSPI Config Related Functions *******************************************************************************/ /* Translates HAL bus width to PDL bus width */ static cy_en_smif_txfr_width_t _cyhal_qspi_convert_bus_width(cyhal_qspi_bus_width_t bus_width) { cy_en_smif_txfr_width_t cyhal_bus_width; switch (bus_width) { case CYHAL_QSPI_CFG_BUS_SINGLE: cyhal_bus_width = CY_SMIF_WIDTH_SINGLE; break; case CYHAL_QSPI_CFG_BUS_DUAL: cyhal_bus_width = CY_SMIF_WIDTH_DUAL; break; case CYHAL_QSPI_CFG_BUS_QUAD: cyhal_bus_width = CY_SMIF_WIDTH_QUAD; break; case CYHAL_QSPI_CFG_BUS_OCTAL: cyhal_bus_width = CY_SMIF_WIDTH_OCTAL; break; default: cyhal_bus_width = CY_SMIF_WIDTH_SINGLE; } return cyhal_bus_width; } /* Translates cyhal_qspi_command_t to cy_stc_smif_mem_cmd_t */ static void _cyhal_qspi_convert_cmd_config(const cyhal_qspi_command_t *qspi_command, cy_stc_smif_mem_cmd_t *const cyhal_cmd_config) { /* This function does not check 'disabled' of each sub-structure in qspi_command_t * It is the responsibility of the caller to check it. */ cyhal_cmd_config->command = qspi_command->instruction.value; cyhal_cmd_config->cmdWidth = _cyhal_qspi_convert_bus_width(qspi_command->instruction.bus_width); cyhal_cmd_config->addrWidth = _cyhal_qspi_convert_bus_width(qspi_command->address.bus_width); cyhal_cmd_config->mode = qspi_command->mode_bits.value; cyhal_cmd_config->modeWidth = _cyhal_qspi_convert_bus_width(qspi_command->mode_bits.bus_width); cyhal_cmd_config->dummyCycles = qspi_command->dummy_count; cyhal_cmd_config->dataWidth = _cyhal_qspi_convert_bus_width(qspi_command->data.bus_width); } static void _cyhal_qspi_uint32_to_byte_array(uint32_t value, uint8_t *byteArray, uint32_t startPos, uint32_t size) { do { size--; byteArray[size + startPos] = (uint8_t)(value & 0xFF); value >>= 0x08; } while (size > 0); } /* cyhal_qspi_size_t to number bytes */ static inline uint32_t _cyhal_qspi_get_size(cyhal_qspi_size_t hal_size) { return ((uint32_t)hal_size >> 3); /* convert bits to bytes */ } /* Sends QSPI command with certain set of data */ /* Address passed through 'command' is not used, instead the value in 'addr' is used. */ static cy_rslt_t _cyhal_qspi_command_transfer(cyhal_qspi_t *obj, const cyhal_qspi_command_t *command, uint32_t addr, bool endOfTransfer) { /* max address size is 4 bytes and max mode bits size is 4 bytes */ uint8_t cmd_param[8] = {0}; uint32_t start_pos = 0; uint32_t addr_size = 0; uint32_t mode_bits_size = 0; cy_en_smif_txfr_width_t bus_width = CY_SMIF_WIDTH_SINGLE; cy_stc_smif_mem_cmd_t cyhal_cmd_config; cy_rslt_t result = CY_RSLT_SUCCESS; _cyhal_qspi_convert_cmd_config(command, &cyhal_cmd_config); /* Does not support different bus_width for address and mode bits. * bus_width is selected based on what (address or mode bits) is enabled. * If both are enabled, bus_width of mode bits is selected * It is either possible to support 1 byte mode bits with different bus_width * by sending the mode byte as command as done in Cy_SMIF_Memslot_CmdRead() * in cyhal_smif_memslot.c or support multiple bytes of mode bits with same bus_width * as address by passing the mode bytes as cmd_param to Cy_SMIF_TransmitCommand(). * Second approach is implemented here. This restriction is because of the way * PDL API is implemented. */ if (!command->address.disabled && !command->mode_bits.disabled) { if (cyhal_cmd_config.addrWidth != cyhal_cmd_config.modeWidth) { result = CYHAL_QSPI_RSLT_ERR_BUS_WIDTH; } } if (CY_RSLT_SUCCESS == result) { if (!command->address.disabled) { addr_size = _cyhal_qspi_get_size(command->address.size); _cyhal_qspi_uint32_to_byte_array(addr, cmd_param, start_pos, addr_size); start_pos += addr_size; bus_width = cyhal_cmd_config.addrWidth; } if (!command->mode_bits.disabled) { mode_bits_size = _cyhal_qspi_get_size(command->mode_bits.size); _cyhal_qspi_uint32_to_byte_array(cyhal_cmd_config.mode, cmd_param, start_pos, mode_bits_size); bus_width = cyhal_cmd_config.modeWidth; } uint32_t cmpltTxfr = ((endOfTransfer) ? 1UL : 0UL); result = (cy_rslt_t)Cy_SMIF_TransmitCommand(obj->base, (uint8_t)cyhal_cmd_config.command, cyhal_cmd_config.cmdWidth, cmd_param, (addr_size + mode_bits_size), bus_width, obj->slave_select, cmpltTxfr, &obj->context); } return result; } static inline cy_en_smif_slave_select_t _cyhal_qspi_slave_idx_to_smif_ss(uint8_t ssel_idx) { return (cy_en_smif_slave_select_t)(1 << ssel_idx); } /* Checks, that user provided all needed pins and returns max bus width */ static cy_rslt_t _cyhal_qspi_check_user_pins(cyhal_qspi_t *obj, cyhal_qspi_bus_width_t *max_width) { cy_rslt_t result = CY_RSLT_SUCCESS; if (NC == obj->pin_sclk) { *max_width = CYHAL_QSPI_CFG_BUS_SINGLE; result = CYHAL_QSPI_RSLT_ERR_PIN; } else { #if _CYHAL_QSPI_DATA8_PRESENT if (NC != obj->pin_ios[4]) { *max_width = CYHAL_QSPI_CFG_BUS_OCTAL; } else #endif if (NC != obj->pin_ios[2]) { *max_width = CYHAL_QSPI_CFG_BUS_QUAD; } else if (NC != obj->pin_ios[1]) { *max_width = CYHAL_QSPI_CFG_BUS_DUAL; } else { *max_width = CYHAL_QSPI_CFG_BUS_SINGLE; } for (uint8_t i = 1; i <= _CYHAL_QSPI_MAX_DATA_PINS; i++) { /* Pins with index lower than width must be provided, pins above should be NC */ if ((NC == obj->pin_ios[i-1]) != (i > *max_width)) { result = CYHAL_QSPI_RSLT_ERR_PIN; } } } return result; } /* Based on ssel pin chosen, determines SMIF slave select parameter and pin mapping */ static const cyhal_resource_pin_mapping_t *_cyhal_qspi_get_slaveselect(cyhal_gpio_t ssel, uint8_t *ssel_idx) { #if _CYHAL_QSPI_SEL1 || _CYHAL_QSPI_SEL2 || _CYHAL_QSPI_SEL3 bool pin_found = false; #endif const cyhal_resource_pin_mapping_t *pin_mapping = _CYHAL_UTILS_GET_RESOURCE(ssel, cyhal_pin_map_smif_spi_select0); if (NULL != pin_mapping) { #if _CYHAL_QSPI_SEL1 || _CYHAL_QSPI_SEL2 || _CYHAL_QSPI_SEL3 pin_found = true; #endif /* Provided by user ssel is related to Slave # 0 */ *ssel_idx = 0; } #if _CYHAL_QSPI_SEL1 if (!pin_found) { pin_mapping = _CYHAL_UTILS_GET_RESOURCE(ssel, cyhal_pin_map_smif_spi_select1); if (NULL != pin_mapping) { pin_found = true; /* Provided by user ssel is related to Slave # 1 */ *ssel_idx = 1; } } #endif #if _CYHAL_QSPI_SEL2 if (!pin_found) { pin_mapping = _CYHAL_UTILS_GET_RESOURCE(ssel, cyhal_pin_map_smif_spi_select2); if (NULL != pin_mapping) { pin_found = true; /* Provided by user ssel is related to Slave # 2 */ *ssel_idx = 2; } } #endif #if _CYHAL_QSPI_SEL3 if (!pin_found) { pin_mapping = _CYHAL_UTILS_GET_RESOURCE(ssel, cyhal_pin_map_smif_spi_select3); if (NULL != pin_mapping) { pin_found = true; /* Provided by user ssel is related to Slave # 3 */ *ssel_idx = 3; } } #endif return pin_mapping; } /* Based on data pins chosen, determines SMIF data select parameter */ static const cyhal_resource_pin_mapping_t *_cyhal_qspi_get_dataselect(cyhal_gpio_t io0, cy_en_smif_data_select_t *data_select, uint8_t *offset) { bool pin_found = false; const cyhal_resource_pin_mapping_t *pin_mapping = _CYHAL_UTILS_GET_RESOURCE(io0, cyhal_pin_map_smif_spi_data0); if (NULL != pin_mapping) { pin_found = true; *data_select = CY_SMIF_DATA_SEL0; *offset = 0; } if (!pin_found) { pin_mapping = _CYHAL_UTILS_GET_RESOURCE(io0, cyhal_pin_map_smif_spi_data2); if (NULL != pin_mapping) { pin_found = true; *data_select = CY_SMIF_DATA_SEL1; *offset = 2; } } #if _CYHAL_QSPI_DATA8_PRESENT if (!pin_found) { pin_mapping = _CYHAL_UTILS_GET_RESOURCE(io0, cyhal_pin_map_smif_spi_data4); if (NULL != pin_mapping) { pin_found = true; *data_select = CY_SMIF_DATA_SEL2; *offset = 4; } } if (!pin_found) { pin_mapping = _CYHAL_UTILS_GET_RESOURCE(io0, cyhal_pin_map_smif_spi_data6); if (NULL != pin_mapping) { pin_found = true; *data_select = CY_SMIF_DATA_SEL3; *offset = 6; } } #endif return pin_mapping; } /******************************************************************************* * Functions *******************************************************************************/ static cy_rslt_t _cyhal_qspi_slave_select_check_reserve(cyhal_qspi_t *obj, cyhal_gpio_t ssel, uint8_t *found_idx, bool *config_needed) { CY_ASSERT(NULL != obj); *config_needed = true; const cyhal_resource_pin_mapping_t *ssel_map = _cyhal_qspi_get_slaveselect(ssel, found_idx); const cyhal_resource_pin_mapping_t *sclk_map = _CYHAL_UTILS_GET_RESOURCE(obj->pin_sclk, cyhal_pin_map_smif_spi_clk); /* Found ssel map is not null and belong to same block as sclk */ if ((NULL == ssel_map) || (NULL == sclk_map) || !_cyhal_utils_resources_equal(sclk_map->inst, ssel_map->inst)) { return CYHAL_QSPI_RSLT_ERR_CANNOT_CONFIG_SSEL; } /* Specified SSEL is already configured */ if (NC != obj->pin_ssel[*found_idx]) { *config_needed = false; return CY_RSLT_SUCCESS; } /* No problems with pins, proceeding to reservation */ cy_rslt_t result = _cyhal_qspi_check_pin_and_reserve(ssel, ssel_map); if (CY_RSLT_SUCCESS == result) { obj->pin_ssel[*found_idx] = ssel; } return result; } cy_rslt_t cyhal_qspi_init( cyhal_qspi_t *obj, cyhal_gpio_t io0, cyhal_gpio_t io1, cyhal_gpio_t io2, cyhal_gpio_t io3, cyhal_gpio_t io4, cyhal_gpio_t io5, cyhal_gpio_t io6, cyhal_gpio_t io7, cyhal_gpio_t sclk, cyhal_gpio_t ssel, uint32_t hz, uint8_t mode) { /* mode (CPOL and CPHA) are not supported in CAT1 (PSoC 6) */ (void)mode; CY_ASSERT(NULL != obj); /* Explicitly marked not allocated resources as invalid to prevent freeing them. */ memset(obj, 0, sizeof(cyhal_qspi_t)); obj->resource.type = CYHAL_RSC_INVALID; obj->is_clock_owned = false; cyhal_qspi_bus_width_t max_width; obj->pin_sclk = sclk; obj->pin_ios[0] = io0; obj->pin_ios[1] = io1; obj->pin_ios[2] = io2; obj->pin_ios[3] = io3; obj->pin_ios[4] = io4; obj->pin_ios[5] = io5; obj->pin_ios[6] = io6; obj->pin_ios[7] = io7; cy_rslt_t result = _cyhal_qspi_check_user_pins(obj, &max_width); for (uint8_t i = 0; i < SMIF_CHIP_TOP_SPI_SEL_NR; ++i) { obj->pin_ssel[i] = NC; } uint8_t pin_offset = 0; const cyhal_resource_pin_mapping_t *sclk_map = _CYHAL_UTILS_GET_RESOURCE(sclk, cyhal_pin_map_smif_spi_clk); const cyhal_resource_pin_mapping_t *io_maps[8] = { NULL }; const size_t data_pin_map_sizes[_CYHAL_QSPI_MAX_DATA_PINS - 1] = // Must compute sizes here since we can't get them from the map pointers { sizeof(cyhal_pin_map_smif_spi_data1) / sizeof(cyhal_resource_pin_mapping_t), sizeof(cyhal_pin_map_smif_spi_data2) / sizeof(cyhal_resource_pin_mapping_t), sizeof(cyhal_pin_map_smif_spi_data3) / sizeof(cyhal_resource_pin_mapping_t), #if _CYHAL_QSPI_DATA8_PRESENT sizeof(cyhal_pin_map_smif_spi_data4) / sizeof(cyhal_resource_pin_mapping_t), sizeof(cyhal_pin_map_smif_spi_data5) / sizeof(cyhal_resource_pin_mapping_t), sizeof(cyhal_pin_map_smif_spi_data6) / sizeof(cyhal_resource_pin_mapping_t), sizeof(cyhal_pin_map_smif_spi_data7) / sizeof(cyhal_resource_pin_mapping_t), #endif }; const cyhal_resource_pin_mapping_t *data_pin_maps[_CYHAL_QSPI_MAX_DATA_PINS - 1] = // Not used to get the map for data pin 0 { cyhal_pin_map_smif_spi_data1, cyhal_pin_map_smif_spi_data2, cyhal_pin_map_smif_spi_data3, #if _CYHAL_QSPI_DATA8_PRESENT cyhal_pin_map_smif_spi_data4, cyhal_pin_map_smif_spi_data5, cyhal_pin_map_smif_spi_data6, cyhal_pin_map_smif_spi_data7, #endif }; if (CY_RSLT_SUCCESS == result) { io_maps[0] = _cyhal_qspi_get_dataselect(obj->pin_ios[0], &(obj->data_select), &pin_offset); if (NULL == sclk_map || NULL == io_maps[0] || !_cyhal_utils_resources_equal(sclk_map->inst, io_maps[0]->inst)) { result = CYHAL_QSPI_RSLT_ERR_PIN; } } if (CY_RSLT_SUCCESS == result) { /* Check that all data pins are valid and belong to same instance */ for (uint8_t i = 1; i < max_width; i++) { io_maps[i] = _cyhal_utils_get_resource(obj->pin_ios[i], data_pin_maps[i - 1 + pin_offset], data_pin_map_sizes[i - 1 + pin_offset], NULL); if (NULL == io_maps[i] || !_cyhal_utils_resources_equal(sclk_map->inst, io_maps[i]->inst)) { result = CYHAL_QSPI_RSLT_ERR_PIN; break; } } } uint8_t found_ssel_idx = 0; bool datasel_cfg_needed = true; if (CY_RSLT_SUCCESS == result) { result = _cyhal_qspi_slave_select_check_reserve(obj, ssel, &found_ssel_idx, &datasel_cfg_needed); } if (CY_RSLT_SUCCESS == result) { /* reserve the SCLK pin */ result = _cyhal_qspi_check_pin_and_reserve(obj->pin_sclk, sclk_map); /* reserve the io pins */ for (uint8_t i = 0; (i < _CYHAL_QSPI_MAX_DATA_PINS) && (result == CY_RSLT_SUCCESS); i++) { if (NC != obj->pin_ios[i]) { result = _cyhal_qspi_check_pin_and_reserve(obj->pin_ios[i], io_maps[i]); } } } #ifndef __MBED__ // Mbed calls qspi_init multiple times without calling qspi_free to update the QSPI frequency/mode. // As a result, we won't worry about resource reservation if running through mbed. if (CY_RSLT_SUCCESS == result) { result = cyhal_hwmgr_reserve(sclk_map->inst); } #endif if (CY_RSLT_SUCCESS == result) { obj->resource = *sclk_map->inst; obj->base = _cyhal_qspi_base_addresses[obj->resource.block_num]; } if (CY_RSLT_SUCCESS == result) { // The hardware is generally going to be hardwired to an hfclk, which has very limited divider options. In the event // that we're hooked up a PERI divider, we don't have any particular expectations about its width - so just ask for 8-bit result = _cyhal_utils_allocate_clock(&(obj->clock), &(obj->resource), CYHAL_CLOCK_BLOCK_PERIPHERAL_8BIT, true); } if(CY_RSLT_SUCCESS == result) { obj->is_clock_owned = true; result = cyhal_qspi_set_frequency(obj, hz); } if(CY_RSLT_SUCCESS == result) { result = cyhal_clock_set_enabled(&(obj->clock), true, true); } if (CY_RSLT_SUCCESS == result) { result = (cy_rslt_t) Cy_SMIF_Init(obj->base, &_cyhal_qspi_default_config, _CYHAL_QSPI_TIMEOUT_10_MS, &obj->context); } if (CY_RSLT_SUCCESS == result) { obj->slave_select = _cyhal_qspi_slave_idx_to_smif_ss(found_ssel_idx); if (datasel_cfg_needed) { Cy_SMIF_SetDataSelect(obj->base, obj->slave_select, obj->data_select); } Cy_SMIF_Enable(obj->base, &obj->context); obj->callback_data.callback = NULL; obj->callback_data.callback_arg = NULL; obj->irq_cause = CYHAL_QSPI_EVENT_NONE; _cyhal_qspi_config_structs[obj->resource.block_num] = obj; obj->pm_transition_pending = false; obj->pm_callback.callback = &_cyhal_qspi_pm_callback; obj->pm_callback.states = (cyhal_syspm_callback_state_t)(CYHAL_SYSPM_CB_CPU_DEEPSLEEP | CYHAL_SYSPM_CB_SYSTEM_HIBERNATE); obj->pm_callback.args = obj; obj->pm_callback.next = NULL; obj->pm_callback.ignore_modes = (cyhal_syspm_callback_mode_t)0; _cyhal_syspm_register_peripheral_callback(&(obj->pm_callback)); cy_stc_sysint_t irqCfg = { _CYHAL_QSPI_IRQ_N[obj->resource.block_num], CYHAL_ISR_PRIORITY_DEFAULT }; Cy_SysInt_Init(&irqCfg, _cyhal_qspi_irq_handler); NVIC_EnableIRQ(_CYHAL_QSPI_IRQ_N[obj->resource.block_num]); } if (CY_RSLT_SUCCESS != result) { cyhal_qspi_free(obj); } return result; } void cyhal_qspi_free(cyhal_qspi_t *obj) { if (CYHAL_RSC_INVALID != obj->resource.type) { IRQn_Type irqn = _CYHAL_QSPI_IRQ_N[obj->resource.block_num]; NVIC_DisableIRQ(irqn); _cyhal_syspm_unregister_peripheral_callback(&(obj->pm_callback)); if (obj->base != NULL) { Cy_SMIF_DeInit(obj->base); obj->base = NULL; } cyhal_hwmgr_free(&(obj->resource)); obj->resource.type = CYHAL_RSC_INVALID; } _cyhal_utils_release_if_used(&(obj->pin_sclk)); for (uint8_t i = 0; i < SMIF_CHIP_TOP_SPI_SEL_NR; i++) { _cyhal_utils_release_if_used(&(obj->pin_ssel[i])); } for (uint8_t i = 0; (i < _CYHAL_QSPI_MAX_DATA_PINS); i++) { _cyhal_utils_release_if_used(&(obj->pin_ios[i])); } if (obj->is_clock_owned) { cyhal_clock_free(&(obj->clock)); obj->is_clock_owned = false; } } cy_rslt_t cyhal_qspi_set_frequency(cyhal_qspi_t *obj, uint32_t hz) { CY_ASSERT(NULL != obj); CY_ASSERT(hz != 0); CY_ASSERT(obj->is_clock_owned); const cyhal_clock_tolerance_t tolerance = { CYHAL_TOLERANCE_PERCENT, 10 }; return _cyhal_utils_set_clock_frequency2(&(obj->clock), hz, &tolerance); } uint32_t cyhal_qspi_get_frequency(cyhal_qspi_t *obj) { CY_ASSERT(NULL != obj); return cyhal_clock_get_frequency(&(obj->clock)); } cy_rslt_t cyhal_qspi_slave_select_config(cyhal_qspi_t *obj, cyhal_gpio_t ssel) { CY_ASSERT(NULL != obj); bool datasel_cfg_needed = false; uint8_t ssel_idx = 0; cy_rslt_t result = _cyhal_qspi_slave_select_check_reserve(obj, ssel, &ssel_idx, &datasel_cfg_needed); if ((CY_RSLT_SUCCESS == result) && datasel_cfg_needed) { Cy_SMIF_SetDataSelect(obj->base, _cyhal_qspi_slave_idx_to_smif_ss(ssel_idx), obj->data_select); } return result; } cy_rslt_t cyhal_qspi_select_active_ssel(cyhal_qspi_t *obj, cyhal_gpio_t ssel) { CY_ASSERT(NULL != obj); for (uint8_t ssel_idx = 0; ssel_idx < SMIF_CHIP_TOP_SPI_SEL_NR; ssel_idx++) { if (ssel == obj->pin_ssel[ssel_idx]) { obj->slave_select = _cyhal_qspi_slave_idx_to_smif_ss(ssel_idx); return CY_RSLT_SUCCESS; } } return CYHAL_QSPI_RSLT_ERR_CANNOT_SWITCH_SSEL; } static cy_rslt_t _cyhal_qspi_wait_for_cmd_fifo(cyhal_qspi_t *obj) { cy_rslt_t status = CY_RSLT_SUCCESS; uint32_t timeout = _CYHAL_QSPI_TIMEOUT_10_MS; while ((Cy_SMIF_GetCmdFifoStatus(obj->base) == CY_SMIF_TX_CMD_FIFO_STATUS_RANGE) && (CY_RSLT_SUCCESS == status)) { /* Waiting for 1 us per iteration */ Cy_SysLib_DelayUs(1); --timeout; status = (0u == timeout)? CYHAL_QSPI_RSLT_ERR_TIMEOUT: CY_RSLT_SUCCESS; } return status; } /* no restriction on the value of length. This function splits the read into multiple chunked transfers. */ cy_rslt_t cyhal_qspi_read(cyhal_qspi_t *obj, const cyhal_qspi_command_t *command, void *data, size_t *length) { if (obj->pm_transition_pending) { return CYHAL_SYSPM_RSLT_ERR_PM_PENDING; } cy_rslt_t status = CY_RSLT_SUCCESS; uint32_t chunk = 0; size_t read_bytes = *length; uint32_t addr = command->address.value; /* SMIF can read only up to 65536 bytes in one go. Split the larger read into multiple chunks */ while (read_bytes > 0) { chunk = (read_bytes > _CYHAL_QSPI_MAX_RX_COUNT) ? (_CYHAL_QSPI_MAX_RX_COUNT) : read_bytes; /* Address is passed outside command during a read of more than 65536 bytes. Since that * read has to be split into multiple chunks, the address value needs to be incremented * after every chunk has been read. This requires either modifying the address stored in * 'command' passed to read() which is not possible since command is a const pointer or * to create a copy of the command object. Instead of copying the object, the address is * passed separately. */ status = _cyhal_qspi_command_transfer(obj, command, addr, false); if (CY_RSLT_SUCCESS == status) { if (command->dummy_count > 0u) { status = _cyhal_qspi_wait_for_cmd_fifo(obj); if (CY_RSLT_SUCCESS == status) { status = (cy_rslt_t)Cy_SMIF_SendDummyCycles(obj->base, command->dummy_count); } } if (CY_RSLT_SUCCESS == status) { status = _cyhal_qspi_wait_for_cmd_fifo(obj); if (CY_RSLT_SUCCESS == status) { status = (cy_rslt_t)Cy_SMIF_ReceiveDataBlocking(obj->base, (uint8_t *)data, chunk, _cyhal_qspi_convert_bus_width(command->data.bus_width), &obj->context); if (CY_RSLT_SUCCESS != status) { break; } } } } read_bytes -= chunk; addr += chunk; data = (uint8_t *)data + chunk; } return status; } cy_rslt_t cyhal_qspi_read_async(cyhal_qspi_t *obj, const cyhal_qspi_command_t *command, void *data, size_t *length) { if (obj->pm_transition_pending) { return CYHAL_SYSPM_RSLT_ERR_PM_PENDING; } cy_rslt_t status = _cyhal_qspi_command_transfer(obj, command, command->address.value, false); if (CY_RSLT_SUCCESS == status) { if (command->dummy_count > 0u) { status = _cyhal_qspi_wait_for_cmd_fifo(obj); if (CY_RSLT_SUCCESS == status) { status = (cy_rslt_t)Cy_SMIF_SendDummyCycles(obj->base, command->dummy_count); } } if (CY_RSLT_SUCCESS == status) { status = _cyhal_qspi_wait_for_cmd_fifo(obj); if (CY_RSLT_SUCCESS == status) { status = (cy_rslt_t)Cy_SMIF_ReceiveData(obj->base, (uint8_t *)data, (uint32_t)*length, _cyhal_qspi_convert_bus_width(command->data.bus_width), _cyhal_qspi_cb_wrapper, &obj->context); } } } return status; } /* length can be up to 65536. */ cy_rslt_t cyhal_qspi_write(cyhal_qspi_t *obj, const cyhal_qspi_command_t *command, const void *data, size_t *length) { if (obj->pm_transition_pending) { return CYHAL_SYSPM_RSLT_ERR_PM_PENDING; } cy_rslt_t status = _cyhal_qspi_command_transfer(obj, command, command->address.value, false); if (CY_RSLT_SUCCESS == status) { if (command->dummy_count > 0u) { status = _cyhal_qspi_wait_for_cmd_fifo(obj); if (CY_RSLT_SUCCESS == status) { status = (cy_rslt_t)Cy_SMIF_SendDummyCycles(obj->base, command->dummy_count); } } if ((CY_SMIF_SUCCESS == status) && (*length > 0)) { status = _cyhal_qspi_wait_for_cmd_fifo(obj); if (CY_RSLT_SUCCESS == status) { status = (cy_rslt_t)Cy_SMIF_TransmitDataBlocking(obj->base, (uint8_t *)data, *length, _cyhal_qspi_convert_bus_width(command->data.bus_width), &obj->context); } } } return status; } /* length can be up to 65536. */ cy_rslt_t cyhal_qspi_write_async(cyhal_qspi_t *obj, const cyhal_qspi_command_t *command, const void *data, size_t *length) { if (obj->pm_transition_pending) { return CYHAL_SYSPM_RSLT_ERR_PM_PENDING; } cy_rslt_t status = _cyhal_qspi_command_transfer(obj, command, command->address.value, false); if (CY_RSLT_SUCCESS == status) { if (command->dummy_count > 0u) { status = _cyhal_qspi_wait_for_cmd_fifo(obj); if (CY_RSLT_SUCCESS == status) { status = (cy_rslt_t)Cy_SMIF_SendDummyCycles(obj->base, command->dummy_count); } } if ((CY_SMIF_SUCCESS == status) && (*length > 0)) { status = _cyhal_qspi_wait_for_cmd_fifo(obj); if (CY_RSLT_SUCCESS == status) { status = (cy_rslt_t)Cy_SMIF_TransmitData(obj->base, (uint8_t *)data, *length, _cyhal_qspi_convert_bus_width(command->data.bus_width), _cyhal_qspi_cb_wrapper, &obj->context); } } } return status; } cy_rslt_t cyhal_qspi_transfer( cyhal_qspi_t *obj, const cyhal_qspi_command_t *command, const void *tx_data, size_t tx_size, void *rx_data, size_t rx_size) { if (obj->pm_transition_pending) { return CYHAL_SYSPM_RSLT_ERR_PM_PENDING; } cy_rslt_t status = CY_RSLT_SUCCESS; if ((tx_data == NULL || tx_size == 0) && (rx_data == NULL || rx_size == 0)) { /* only command, no rx or tx */ status = _cyhal_qspi_command_transfer(obj, command, command->address.value, true); } else { if (tx_data != NULL && tx_size) { status = cyhal_qspi_write(obj, command, tx_data, &tx_size); } if (status == CY_RSLT_SUCCESS) { if (rx_data != NULL && rx_size) { status = cyhal_qspi_read(obj, command, rx_data, &rx_size); } } } return status; } void cyhal_qspi_register_callback(cyhal_qspi_t *obj, cyhal_qspi_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); obj->irq_cause = CYHAL_QSPI_EVENT_NONE; } void cyhal_qspi_enable_event(cyhal_qspi_t *obj, cyhal_qspi_event_t event, uint8_t intr_priority, bool enable) { if (enable) { obj->irq_cause |= event; } else { obj->irq_cause &= ~event; } IRQn_Type irqn = _CYHAL_QSPI_IRQ_N[obj->resource.block_num]; NVIC_SetPriority(irqn, intr_priority); } #if defined(__cplusplus) } #endif /* __cplusplus */ #endif /* CY_IP_MXSMIF */