mirror of https://github.com/ARMmbed/mbed-os.git
502 lines
16 KiB
C
Executable File
502 lines
16 KiB
C
Executable File
/*
|
|
* mbed Microcontroller Library
|
|
* Copyright (c) 2018-2019 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.
|
|
*/
|
|
|
|
#if DEVICE_QSPI
|
|
|
|
#include "qspi_api.h"
|
|
#include "mbed_error.h"
|
|
#include "mbed_assert.h"
|
|
#include "cmsis.h"
|
|
#include "pinmap.h"
|
|
#include "PeripheralPins.h"
|
|
#include "cy_smif_memslot.h"
|
|
#include "cy_smif.h"
|
|
|
|
#define TIMEOUT_10_MS (10000UL) /* in microseconds, timeout for all blocking functions */
|
|
#define SMIF_MAX_RX_COUNT (65536UL)
|
|
#define QSPI_DESELECT_DELAY (7UL)
|
|
|
|
#define QSPI_PIN_MAP_IDX0 (0UL)
|
|
#define QSPI_PIN_MAP_IDX1 (1UL)
|
|
#define QSPI_PIN_MAP_IDX2 (2UL)
|
|
#define QSPI_PIN_MAP_IDX3 (3UL)
|
|
#define QSPI_PIN_MAP_IDX4 (4UL)
|
|
#define QSPI_PIN_MAP_IDX5 (5UL)
|
|
#define QSPI_PIN_MAP_IDX6 (6UL)
|
|
#define QSPI_PIN_MAP_IDX7 (7UL)
|
|
|
|
/* Default QSPI configuration */
|
|
static const cy_stc_smif_config_t default_qspi_config =
|
|
{
|
|
.mode = (uint32_t)CY_SMIF_NORMAL,
|
|
.deselectDelay = QSPI_DESELECT_DELAY,
|
|
.rxClockSel = (uint32_t)CY_SMIF_SEL_INV_INTERNAL_CLK,
|
|
.blockEvent = (uint32_t)CY_SMIF_BUS_ERROR,
|
|
};
|
|
|
|
|
|
static uint32_t get_addr_size(qspi_address_size_t size)
|
|
{
|
|
uint32_t addr_size = 0;
|
|
|
|
switch(size) {
|
|
case QSPI_CFG_ADDR_SIZE_8:
|
|
addr_size = 1;
|
|
break;
|
|
|
|
case QSPI_CFG_ADDR_SIZE_16:
|
|
addr_size = 2;
|
|
break;
|
|
|
|
case QSPI_CFG_ADDR_SIZE_24:
|
|
addr_size = 3;
|
|
break;
|
|
|
|
case QSPI_CFG_ADDR_SIZE_32:
|
|
addr_size = 4;
|
|
break;
|
|
|
|
default:
|
|
addr_size = 0;
|
|
}
|
|
|
|
return addr_size;
|
|
}
|
|
|
|
static uint32_t get_alt_size(qspi_alt_size_t size)
|
|
{
|
|
uint32_t alt_size = 0;
|
|
|
|
switch(size) {
|
|
case QSPI_CFG_ALT_SIZE_8:
|
|
alt_size = 1;
|
|
break;
|
|
|
|
case QSPI_CFG_ALT_SIZE_16:
|
|
alt_size = 2;
|
|
break;
|
|
|
|
case QSPI_CFG_ALT_SIZE_24:
|
|
alt_size = 3;
|
|
break;
|
|
|
|
case QSPI_CFG_ALT_SIZE_32:
|
|
alt_size = 4;
|
|
break;
|
|
|
|
default:
|
|
alt_size = 0;
|
|
}
|
|
|
|
return alt_size;
|
|
}
|
|
|
|
static cy_en_smif_txfr_width_t get_cy_bus_width(qspi_bus_width_t bus_width)
|
|
{
|
|
cy_en_smif_txfr_width_t cy_bus_width = CY_SMIF_WIDTH_SINGLE;
|
|
|
|
switch(bus_width) {
|
|
case QSPI_CFG_BUS_SINGLE:
|
|
cy_bus_width = CY_SMIF_WIDTH_SINGLE;
|
|
break;
|
|
|
|
case QSPI_CFG_BUS_DUAL:
|
|
cy_bus_width = CY_SMIF_WIDTH_DUAL;
|
|
break;
|
|
|
|
case QSPI_CFG_BUS_QUAD:
|
|
cy_bus_width = CY_SMIF_WIDTH_QUAD;
|
|
break;
|
|
|
|
default:
|
|
cy_bus_width = CY_SMIF_WIDTH_SINGLE;
|
|
}
|
|
|
|
return cy_bus_width;
|
|
}
|
|
|
|
static void convert_to_cy_cmd_config(const qspi_command_t *qspi_command, cy_stc_smif_mem_cmd_t *const cy_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.
|
|
cy_cmd_config->command = qspi_command->instruction.value;
|
|
cy_cmd_config->cmdWidth = get_cy_bus_width(qspi_command->instruction.bus_width);
|
|
cy_cmd_config->addrWidth = get_cy_bus_width(qspi_command->address.bus_width);
|
|
cy_cmd_config->mode = qspi_command->alt.value;
|
|
cy_cmd_config->modeWidth = get_cy_bus_width(qspi_command->alt.bus_width);
|
|
cy_cmd_config->dummyCycles = qspi_command->dummy_count;
|
|
cy_cmd_config->dataWidth = get_cy_bus_width(qspi_command->data.bus_width);
|
|
|
|
}
|
|
|
|
static void uint32_to_byte_array(uint32_t value, uint8_t *byteArray, uint32_t startPos, uint32_t size)
|
|
{
|
|
do {
|
|
size--;
|
|
byteArray[size + startPos] = (uint8_t)value;
|
|
value >>= 0x08;
|
|
} while(size > 0);
|
|
}
|
|
|
|
static qspi_status_t validate_and_find_slaveselect(cy_en_smif_slave_select_t *slaveSelect, PinName ssel){
|
|
if (ssel == PinMap_QSPI_SSEL[QSPI_PIN_MAP_IDX0].pin) {
|
|
*slaveSelect = CY_SMIF_SLAVE_SELECT_0;
|
|
} else if(ssel == PinMap_QSPI_SSEL[QSPI_PIN_MAP_IDX1].pin) {
|
|
*slaveSelect = CY_SMIF_SLAVE_SELECT_1;
|
|
} else if(ssel == PinMap_QSPI_SSEL[QSPI_PIN_MAP_IDX2].pin) {
|
|
*slaveSelect = CY_SMIF_SLAVE_SELECT_2;
|
|
} else if(ssel == PinMap_QSPI_SSEL[QSPI_PIN_MAP_IDX3].pin) {
|
|
*slaveSelect = CY_SMIF_SLAVE_SELECT_3;
|
|
} else {
|
|
MBED_ERROR( MBED_MAKE_ERROR(MBED_MODULE_APPLICATION, MBED_ERROR_CODE_INVALID_ARGUMENT), "Invalid slave select (ssel) pin." );
|
|
return QSPI_STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
return QSPI_STATUS_OK;
|
|
}
|
|
|
|
static qspi_status_t validate_pins_and_find_dataselect(cy_en_smif_data_select_t *dataSelect, PinName io0, PinName io1, PinName io2, PinName io3, PinName sclk){
|
|
bool isQuadSpi = false;
|
|
|
|
// ----------------------
|
|
// io0 | DATA_SELx
|
|
// ----------------------
|
|
// spi_data0 | DATA_SEL0
|
|
// spi_data2 | DATA_SEL1
|
|
// spi_data4 | DATA_SEL2
|
|
// spi_data6 | DATA_SEL3
|
|
// ----------------------
|
|
//
|
|
// valid combinations (io0, io1, io2, io3)
|
|
// ----------------------------
|
|
// spi_data0, spi_data1, NC, NC - single SPI or Dual SPI
|
|
// spi_data2, spi_data3, NC, NC - single SPI or Dual SPI
|
|
// spi_data4, spi_data5, NC, NC - single SPI or Dual SPI
|
|
// spi_data6, spi_data7, NC, NC - single SPI or Dual SPI
|
|
// spi_data0, spi_data1, spi_data2, spi_data3, ssel0 - Quad SPI
|
|
// spi_data4, spi_data5, spi_data6, spi_data7, ssel2 - Quad SPI
|
|
|
|
if (sclk != PinMap_QSPI_SCLK[QSPI_PIN_MAP_IDX0].pin) {
|
|
goto qspi_pin_error;
|
|
}
|
|
|
|
if ( (io2 != NC) && (io3 != NC) ){
|
|
isQuadSpi = true;
|
|
}
|
|
|
|
if (io0 == PinMap_QSPI_DATA[QSPI_PIN_MAP_IDX0].pin) {
|
|
if (io1 != PinMap_QSPI_DATA[QSPI_PIN_MAP_IDX1].pin) {
|
|
goto qspi_pin_error;
|
|
}
|
|
|
|
if (isQuadSpi) {
|
|
if ((io2 != PinMap_QSPI_DATA[QSPI_PIN_MAP_IDX2].pin) || (io3 != PinMap_QSPI_DATA[QSPI_PIN_MAP_IDX3].pin)) {
|
|
goto qspi_pin_error;
|
|
}
|
|
}
|
|
|
|
*dataSelect = CY_SMIF_DATA_SEL0;
|
|
} else if(io0 == PinMap_QSPI_DATA[QSPI_PIN_MAP_IDX2].pin) {
|
|
// quad spi is not valid in this case
|
|
if (io1 != PinMap_QSPI_DATA[QSPI_PIN_MAP_IDX3].pin) {
|
|
goto qspi_pin_error;
|
|
}
|
|
|
|
*dataSelect = CY_SMIF_DATA_SEL1;
|
|
} else if(io0 == PinMap_QSPI_DATA[QSPI_PIN_MAP_IDX4].pin) {
|
|
if (io1 != PinMap_QSPI_DATA[QSPI_PIN_MAP_IDX5].pin) {
|
|
goto qspi_pin_error;
|
|
}
|
|
|
|
if (isQuadSpi){
|
|
if ((io2 != PinMap_QSPI_DATA[QSPI_PIN_MAP_IDX6].pin) || (io3 != PinMap_QSPI_DATA[QSPI_PIN_MAP_IDX7].pin)) {
|
|
goto qspi_pin_error;
|
|
}
|
|
}
|
|
|
|
*dataSelect = CY_SMIF_DATA_SEL2;
|
|
} else if(io0 == PinMap_QSPI_DATA[QSPI_PIN_MAP_IDX6].pin) {
|
|
// quad spi is not valid in this case
|
|
if (io1 != PinMap_QSPI_DATA[QSPI_PIN_MAP_IDX7].pin) {
|
|
goto qspi_pin_error;
|
|
}
|
|
|
|
*dataSelect = CY_SMIF_DATA_SEL3;
|
|
} else {
|
|
goto qspi_pin_error; // invalid io0
|
|
}
|
|
|
|
return QSPI_STATUS_OK;
|
|
|
|
qspi_pin_error:
|
|
MBED_ERROR( MBED_MAKE_ERROR(MBED_MODULE_APPLICATION, MBED_ERROR_CODE_INVALID_ARGUMENT),
|
|
"Invalid QSPI pin list. For single or dual SPI, io0 & io1 must be valid and io2 & io3 must be NC. For quad SPI, all pins must be valid." );
|
|
return QSPI_STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
qspi_status_t qspi_init(qspi_t *obj, PinName io0, PinName io1, PinName io2, PinName io3, PinName sclk, PinName ssel, uint32_t hz, uint8_t mode)
|
|
{
|
|
cy_en_smif_data_select_t dataSelect = CY_SMIF_DATA_SEL0;
|
|
|
|
MBED_ASSERT(obj);
|
|
MBED_ASSERT(io0 != (PinName)NC);
|
|
MBED_ASSERT(io1 != (PinName)NC);
|
|
|
|
// single SPI or Dual SPI - io0 & io1 must be valid and io2 & io3 must be NC.
|
|
// Quad SPI - all IOs must be valid.
|
|
if (io2 != (PinName)NC) {
|
|
MBED_ASSERT(io3 != (PinName)NC);
|
|
} else {
|
|
MBED_ASSERT(io3 == (PinName)NC);
|
|
}
|
|
|
|
MBED_ASSERT(sclk != (PinName)NC);
|
|
MBED_ASSERT(ssel != (PinName)NC);
|
|
|
|
// mode (CPOL and CPHA) is not supported in PSoC 6
|
|
MBED_ASSERT(mode == 0u);
|
|
|
|
// No pin-wise instance check is done since PSoC 6 has only one SMIF instance.
|
|
// But all the pins are checked against the pin map
|
|
if ( QSPI_STATUS_OK != validate_and_find_slaveselect(&obj->slaveSelect, ssel) ) {
|
|
return QSPI_STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (QSPI_STATUS_OK != validate_pins_and_find_dataselect(&dataSelect, io0, io1, io2, io3, sclk)) {
|
|
return QSPI_STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
// configure drive mode for the pins
|
|
pin_function(io0, pinmap_function(io0, PinMap_QSPI_DATA));
|
|
pin_function(io1, pinmap_function(io1, PinMap_QSPI_DATA));
|
|
pin_function(io2, pinmap_function(io2, PinMap_QSPI_DATA));
|
|
pin_function(io3, pinmap_function(io3, PinMap_QSPI_DATA));
|
|
pin_function(sclk, pinmap_function(sclk, PinMap_QSPI_SCLK));
|
|
pin_function(ssel, pinmap_function(ssel, PinMap_QSPI_SSEL));
|
|
|
|
obj->base = SMIF0;
|
|
cy_en_smif_status_t smif_status = Cy_SMIF_Init(obj->base, &default_qspi_config, TIMEOUT_10_MS, &obj->context);
|
|
if (CY_SMIF_SUCCESS != smif_status) {
|
|
return QSPI_STATUS_ERROR;
|
|
}
|
|
|
|
Cy_SMIF_SetDataSelect(obj->base, obj->slaveSelect, dataSelect); // Configures data select
|
|
Cy_SMIF_Enable(obj->base, &obj->context);
|
|
|
|
return qspi_frequency(obj, hz);
|
|
}
|
|
|
|
qspi_status_t qspi_free(qspi_t *obj)
|
|
{
|
|
Cy_SMIF_DeInit(obj->base);
|
|
return QSPI_STATUS_OK;
|
|
}
|
|
|
|
qspi_status_t qspi_frequency(qspi_t *obj, int hz)
|
|
{
|
|
// Not implemented yet
|
|
// PSoC supports only divide by 1, 2, 4, and 8.
|
|
|
|
return QSPI_STATUS_OK;
|
|
}
|
|
|
|
// Address passed through 'command' is not used, instead the value in 'addr' is used.
|
|
static qspi_status_t smif_command_transfer(qspi_t *obj, const qspi_command_t *command, uint32_t addr, bool endOfTransfer)
|
|
{
|
|
// Does not support command->instruction.diabled
|
|
if (command->instruction.disabled) {
|
|
return QSPI_STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
uint8_t cmd_param[8] = {0}; // max address size is 4 bytes and max alt size is 4 bytes
|
|
uint32_t startPos = 0;
|
|
uint32_t addr_size = 0;
|
|
uint32_t alt_size = 0;
|
|
cy_en_smif_txfr_width_t bus_width = CY_SMIF_WIDTH_SINGLE;
|
|
cy_stc_smif_mem_cmd_t cy_cmd_config;
|
|
|
|
convert_to_cy_cmd_config(command, &cy_cmd_config);
|
|
|
|
// Does not support different bus_width for address and alt.
|
|
// bus_width is selected based on what (address or alt) is enabled.
|
|
// If both are enabled, bus_width of alt is selected.
|
|
|
|
// It is either possible to support 1 byte alt with different bus_width
|
|
// by sending the alt byte as command as done in Cy_SMIF_Memslot_CmdRead()
|
|
// in cy_smif_memslot.c or support multiple bytes of alt with same bus_width
|
|
// as address by passing the alt 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->alt.disabled) {
|
|
if (cy_cmd_config.addrWidth != cy_cmd_config.modeWidth) {
|
|
return QSPI_STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
if (!command->address.disabled) {
|
|
addr_size = get_addr_size(command->address.size);
|
|
uint32_to_byte_array(addr, cmd_param, startPos, addr_size);
|
|
startPos += addr_size;
|
|
bus_width = cy_cmd_config.addrWidth;
|
|
}
|
|
|
|
if (!command->alt.disabled) {
|
|
alt_size = get_alt_size(command->alt.size);
|
|
uint32_to_byte_array(cy_cmd_config.mode, cmd_param, startPos, alt_size);
|
|
bus_width = cy_cmd_config.modeWidth;
|
|
}
|
|
|
|
uint32_t cmpltTxfr = ((endOfTransfer) ? 1UL : 0UL);
|
|
cy_en_smif_status_t smifStatus = Cy_SMIF_TransmitCommand( obj->base, cy_cmd_config.command,
|
|
cy_cmd_config.cmdWidth, cmd_param, (addr_size + alt_size),
|
|
bus_width, obj->slaveSelect, cmpltTxfr, &obj->context);
|
|
|
|
if (CY_SMIF_SUCCESS != smifStatus) {
|
|
return QSPI_STATUS_ERROR;
|
|
}
|
|
|
|
return QSPI_STATUS_OK;
|
|
}
|
|
|
|
// length can be up to 65536.
|
|
qspi_status_t qspi_write(qspi_t *obj, const qspi_command_t *command, const void *data, size_t *length)
|
|
{
|
|
cy_en_smif_status_t smifStatus = CY_SMIF_SUCCESS;
|
|
qspi_status_t status = smif_command_transfer(obj, command, command->address.value, false);
|
|
|
|
if (QSPI_STATUS_OK == status) {
|
|
if (command->dummy_count > 0u) {
|
|
smifStatus = Cy_SMIF_SendDummyCycles(obj->base, command->dummy_count);
|
|
}
|
|
|
|
if (CY_SMIF_SUCCESS == smifStatus) {
|
|
smifStatus = Cy_SMIF_TransmitDataBlocking(obj->base, (uint8_t *)data, *length,
|
|
get_cy_bus_width(command->data.bus_width), &obj->context);
|
|
|
|
if (CY_SMIF_SUCCESS != smifStatus) {
|
|
status = QSPI_STATUS_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
// no restriction on the value of length. This function splits the read into multiple
|
|
// chunked transfers.
|
|
qspi_status_t qspi_read(qspi_t *obj, const qspi_command_t *command, void *data, size_t *length)
|
|
{
|
|
cy_en_smif_status_t smifStatus = CY_SMIF_SUCCESS;
|
|
qspi_status_t status = QSPI_STATUS_OK;
|
|
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 > SMIF_MAX_RX_COUNT) ? (SMIF_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 = smif_command_transfer(obj, command, addr, false);
|
|
|
|
if (QSPI_STATUS_OK == status) {
|
|
if (command->dummy_count > 0u) {
|
|
smifStatus = Cy_SMIF_SendDummyCycles(obj->base, command->dummy_count);
|
|
}
|
|
|
|
if (CY_SMIF_SUCCESS == smifStatus) {
|
|
smifStatus = Cy_SMIF_ReceiveDataBlocking(obj->base, (uint8_t *)data, chunk,
|
|
get_cy_bus_width(command->data.bus_width), &obj->context);
|
|
if (CY_SMIF_SUCCESS != smifStatus) {
|
|
status = QSPI_STATUS_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
read_bytes -= chunk;
|
|
addr += chunk;
|
|
data = (uint8_t *)data + chunk;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
qspi_status_t qspi_command_transfer(qspi_t *obj, const qspi_command_t *command, const void *tx_data, size_t tx_size, void *rx_data, size_t rx_size)
|
|
{
|
|
qspi_status_t status = QSPI_STATUS_OK;
|
|
|
|
if ((tx_data == NULL || tx_size == 0) && (rx_data == NULL || rx_size == 0)) {
|
|
// only command, no rx or tx
|
|
status = smif_command_transfer(obj, command, command->address.value, true);
|
|
}
|
|
else {
|
|
if (tx_data != NULL && tx_size) {
|
|
status = qspi_write(obj, command, tx_data, &tx_size);
|
|
if (status != QSPI_STATUS_OK) {
|
|
return status;
|
|
}
|
|
}
|
|
|
|
if (rx_data != NULL && rx_size) {
|
|
status = qspi_read(obj, command, rx_data, &rx_size);
|
|
}
|
|
}
|
|
return status;
|
|
}
|
|
|
|
const PinMap *qspi_master_sclk_pinmap(void)
|
|
{
|
|
return PinMap_QSPI_SCLK;
|
|
}
|
|
|
|
const PinMap *qspi_master_ssel_pinmap(void)
|
|
{
|
|
return PinMap_QSPI_SSEL;
|
|
}
|
|
|
|
const PinMap *qspi_master_data0_pinmap(void)
|
|
{
|
|
return PinMap_QSPI_DATA;
|
|
}
|
|
|
|
const PinMap *qspi_master_data1_pinmap(void)
|
|
{
|
|
return PinMap_QSPI_DATA;
|
|
}
|
|
|
|
const PinMap *qspi_master_data2_pinmap(void)
|
|
{
|
|
return PinMap_QSPI_DATA;
|
|
}
|
|
|
|
const PinMap *qspi_master_data3_pinmap(void)
|
|
{
|
|
return PinMap_QSPI_DATA;
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
/** @}*/
|