/* * mbed Microcontroller Library * Copyright (c) 2017-2018 Future Electronics * 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. */ #include "cmsis.h" #include "mbed_assert.h" #include "mbed_error.h" #include "mbed_debug.h" #include "PeripheralPins.h" #include "pinmap.h" #include "spi_api.h" #include "psoc6_utils.h" #include "cy_sysclk.h" #include "cy_gpio.h" #include "cy_scb_spi.h" #include "cy_sysint.h" #define SPI_DEFAULT_SPEED 100000 #define NUM_SPI_PORTS 8 #define SPI_DEFAULT_IRQ_PRIORITY 3 #define SPI_OVERSAMPLE 4 /* 4..16 */ /* Default timeout in milliseconds */ #define SPI_DEFAULT_TIMEOUT 1000 #define PENDING_NONE 0 #define PENDING_RX 1 #define PENDING_TX 2 #define PENDING_TX_RX 3 /* Default SPI configuration */ static const cy_stc_scb_spi_config_t default_spi_config = { .spiMode = CY_SCB_SPI_MASTER, .subMode = CY_SCB_SPI_MOTOROLA, .sclkMode = CY_SCB_SPI_CPHA0_CPOL0, .oversample = SPI_OVERSAMPLE, .rxDataWidth = 8, .txDataWidth = 8, .enableMsbFirst = true, .enableFreeRunSclk = false, .enableInputFilter = false, .enableMisoLateSample = false, .enableTransferSeperation = false, .enableWakeFromSleep = false, .ssPolarity = CY_SCB_SPI_ACTIVE_LOW, .rxFifoTriggerLevel = 0, .rxFifoIntEnableMask = 0, .txFifoTriggerLevel = 0, .txFifoIntEnableMask = 0, .masterSlaveIntEnableMask = 0 }; typedef struct spi_s spi_obj_t; #if DEVICE_SPI_ASYNCH #define OBJ_P(in) (&(in->spi)) #else #define OBJ_P(in) (in) #endif /* DEVICE_SPI_ASYNCH */ #if DEVICE_SPI_ASYNCH /** Allocates channel for SPI interrupt. * * @param obj The serial object */ static IRQn_Type spi_irq_allocate_channel(spi_obj_t *obj) { #if defined (TARGET_MCU_PSOC6_M0) obj->cm0p_irq_src = scb_0_interrupt_IRQn + obj->spi_id; return cy_m0_nvic_allocate_channel(CY_SERIAL_IRQN_ID + obj->spi_id); #else return (IRQn_Type)(scb_0_interrupt_IRQn + obj->spi_id); #endif /* (TARGET_MCU_PSOC6_M0) */ } /** Setup channel for SPI interrupt and enable interrupt in the NVIC. * * @param obj The serial object */ static int spi_irq_setup_channel(spi_obj_t *obj) { cy_stc_sysint_t irq_config; if (obj->irqn == unconnected_IRQn) { IRQn_Type irqn = spi_irq_allocate_channel(obj); if (irqn < 0) { return (-1); } /* Configure NVIC */ irq_config.intrPriority = SPI_DEFAULT_IRQ_PRIORITY; irq_config.intrSrc = irqn; #if defined (TARGET_MCU_PSOC6_M0) irq_config.cm0pSrc = obj->cm0p_irq_src; #endif if (Cy_SysInt_Init(&irq_config, (cy_israddress)(obj->handler)) != CY_SYSINT_SUCCESS) { return (-1); } obj->irqn = irqn; NVIC_EnableIRQ(irqn); } return 0; } #endif /* DEVICE_SPI_ASYNCH */ /** Allocates 16-bit divider for SPI. * * @param obj The serial object */ static int allocate_divider(spi_obj_t *obj) { if (obj->div_num == CY_INVALID_DIVIDER) { obj->div_type = CY_SYSCLK_DIV_16_BIT; obj->div_num = cy_clk_allocate_divider(CY_SYSCLK_DIV_16_BIT); } return (obj->div_num == CY_INVALID_DIVIDER) ? -1 : 0; } /** Initializes spi clock for the required speed * * @param obj The serial object */ static cy_en_sysclk_status_t spi_init_clock(spi_obj_t *obj, uint32_t frequency) { cy_en_sysclk_status_t status = CY_SYSCLK_INVALID_STATE; uint32_t div_value; if (obj->div_num == CY_INVALID_DIVIDER) { if (allocate_divider(obj) < 0) { error("spi: cannot allocate clock divider."); } /* Assign divider after it was allocated */ status = Cy_SysClk_PeriphAssignDivider(obj->clock, obj->div_type, obj->div_num); if (status != CY_SYSCLK_SUCCESS) { error("spi: cannot assign clock divider."); } } /* Set up proper frequency; round up the divider so the frequency is not higher than specified. */ div_value = (cy_PeriClkFreqHz + frequency * (SPI_OVERSAMPLE - 1)) / frequency / SPI_OVERSAMPLE; obj->clk_frequency = cy_PeriClkFreqHz / div_value / SPI_OVERSAMPLE; Cy_SysClk_PeriphDisableDivider(obj->div_type, obj->div_num); if (Cy_SysClk_PeriphSetDivider(obj->div_type, obj->div_num, div_value) != CY_SYSCLK_SUCCESS) { obj->div_num = CY_INVALID_DIVIDER; } Cy_SysClk_PeriphEnableDivider(obj->div_type, obj->div_num); return CY_SYSCLK_SUCCESS; } /** Initializes i/o pins for spi. * * @param obj The serial object */ static void spi_init_pins(spi_obj_t *obj) { if (obj->pin_sclk != NC) { if ((0 != cy_reserve_io_pin(obj->pin_sclk)) && !obj->already_reserved) { error("SPI SCLK pin reservation conflict."); } pin_function(obj->pin_sclk, pinmap_function(obj->pin_sclk, PinMap_SPI_SCLK)); } if (obj->pin_mosi != NC) { if ((0 != cy_reserve_io_pin(obj->pin_mosi)) && !obj->already_reserved) { error("SPI MOSI pin reservation conflict."); } pin_function(obj->pin_mosi, pinmap_function(obj->pin_mosi, PinMap_SPI_MOSI)); } if (obj->pin_miso != NC) { if ((0 != cy_reserve_io_pin(obj->pin_miso)) && !obj->already_reserved) { error("SPI MISO pin reservation conflict."); } pin_function(obj->pin_miso, pinmap_function(obj->pin_miso, PinMap_SPI_MISO)); } if (obj->pin_ssel != NC) { if ((0 != cy_reserve_io_pin(obj->pin_ssel)) && !obj->already_reserved) { error("SPI SSEL pin reservation conflict."); } pin_function(obj->pin_ssel, pinmap_function(obj->pin_ssel, PinMap_SPI_SSEL)); } /* Pin configuration in PinMap defaults to Master mode; revert for Slave */ if (obj->ms_mode == CY_SCB_SPI_SLAVE) { pin_mode(obj->pin_sclk, PullNone); pin_mode(obj->pin_mosi, PullNone); pin_mode(obj->pin_miso, PushPull); pin_mode(obj->pin_ssel, PullNone); } } /** Initializes and enables SPI/SCB. * * @param obj The serial object */ static void spi_init_peripheral(spi_obj_t *obj) { cy_stc_scb_spi_config_t spi_config = default_spi_config; spi_config.spiMode = obj->ms_mode; spi_config.sclkMode = obj->clk_mode; spi_config.rxDataWidth = obj->data_bits; spi_config.txDataWidth = obj->data_bits; Cy_SCB_SPI_Init(obj->base, &spi_config, &obj->context); Cy_SCB_SPI_Enable(obj->base); } /* Callback function to handle into and out of deep sleep state transitions. * */ #if DEVICE_SLEEP && DEVICE_LPTICKER static cy_en_syspm_status_t spi_pm_callback(cy_stc_syspm_callback_params_t *callback_params, cy_en_syspm_callback_mode_t mode) { cy_stc_syspm_callback_params_t params = *callback_params; spi_obj_t *obj = (spi_obj_t *)params.context; params.context = &obj->context; return Cy_SCB_SPI_DeepSleepCallback(¶ms, mode); } #endif /* DEVICE_SLEEP && DEVICE_LPTICKER */ void spi_init(spi_t *obj_in, PinName mosi, PinName miso, PinName sclk, PinName ssel) { spi_obj_t *obj = OBJ_P(obj_in); uint32_t spi = (uint32_t)NC; en_clk_dst_t clock = PCLK_SCB0_CLOCK; if (mosi != NC) { spi = pinmap_merge(spi, pinmap_peripheral(mosi, PinMap_SPI_MOSI)); clock = CY_PIN_CLOCK(pinmap_function(mosi, PinMap_SPI_MOSI)); } if (miso != NC) { spi = pinmap_merge(spi, pinmap_peripheral(miso, PinMap_SPI_MISO)); clock = CY_PIN_CLOCK(pinmap_function(miso, PinMap_SPI_MISO)); } if (sclk != NC) { spi = pinmap_merge(spi, pinmap_peripheral(sclk, PinMap_SPI_SCLK)); clock = CY_PIN_CLOCK(pinmap_function(sclk, PinMap_SPI_SCLK)); } if (ssel != NC) { spi = pinmap_merge(spi, pinmap_peripheral(ssel, PinMap_SPI_SSEL)); clock = CY_PIN_CLOCK(pinmap_function(ssel, PinMap_SPI_SSEL)); } if (spi != (uint32_t)NC) { /* Initialize configuration */ obj->base = (CySCB_Type *)spi; obj->spi_id = ((SPIName)spi - SPI_0) / (SPI_1 - SPI_0); obj->clock = clock; obj->div_num = CY_INVALID_DIVIDER; obj->already_reserved = (0 != cy_reserve_scb(obj->spi_id)); obj->pin_mosi = mosi; obj->pin_miso = miso; obj->pin_sclk = sclk; obj->pin_ssel = ssel; obj->ms_mode = CY_SCB_SPI_MASTER; obj->data_bits = 8; #if DEVICE_SPI_ASYNCH obj->irqn = unconnected_IRQn; obj->pending = PENDING_NONE; obj->events = 0; obj->tx_buffer = NULL; obj->rx_buffer = NULL; obj->tx_buffer_size = 0; obj->rx_buffer_size = 0; #endif /* (DEVICE_SPI_ASYNCH) */ /* Check if resource severed */ if (obj->already_reserved) { uint32_t map; /* SCB pins and clocks are connected */ /* Disable block and get it into the default state */ Cy_SCB_SPI_Disable(obj->base, &obj->context); Cy_SCB_SPI_DeInit(obj->base); /* Get connected clock */ map = Cy_SysClk_PeriphGetAssignedDivider(obj->clock); obj->div_num = _FLD2VAL(CY_PERI_CLOCK_CTL_DIV_SEL, map); obj->div_type = (cy_en_divider_types_t) _FLD2VAL(CY_PERI_CLOCK_CTL_TYPE_SEL, map); } else { #if DEVICE_SLEEP && DEVICE_LPTICKER /* Register callback once */ obj->pm_callback_handler.callback = spi_pm_callback; obj->pm_callback_handler.type = CY_SYSPM_DEEPSLEEP; obj->pm_callback_handler.skipMode = 0; obj->pm_callback_handler.callbackParams = &obj->pm_callback_params; obj->pm_callback_params.base = obj->base; obj->pm_callback_params.context = obj; if (!Cy_SysPm_RegisterCallback(&obj->pm_callback_handler)) { error("PM callback registration failed!"); } #endif /* DEVICE_SLEEP && DEVICE_LPTICKER */ } /* Configure hardware resources */ spi_init_clock(obj, SPI_DEFAULT_SPEED); spi_init_pins(obj); spi_init_peripheral(obj); } else { error("SPI pinout mismatch. Requested SCLK/MOSI/MISO/SSEL pins can't be used for the same SPI communication."); } } void spi_free(spi_t *obj) { error("This function is not supported."); } void spi_format(spi_t *obj_in, int bits, int mode, int slave) { MBED_ASSERT((bits >= 4) && (bits <= 16)); spi_obj_t *obj = OBJ_P(obj_in); cy_en_scb_spi_mode_t new_mode = slave ? CY_SCB_SPI_SLAVE : CY_SCB_SPI_MASTER; Cy_SCB_SPI_Disable(obj->base, &obj->context); obj->data_bits = bits; obj->clk_mode = (cy_en_scb_spi_sclk_mode_t)(mode & 0x3); if (obj->ms_mode != new_mode) { obj->ms_mode = new_mode; spi_init_pins(obj); } spi_init_peripheral(obj); } void spi_frequency(spi_t *obj_in, int hz) { spi_obj_t *obj = OBJ_P(obj_in); Cy_SCB_SPI_Disable(obj->base, &obj->context); spi_init_clock(obj, hz); Cy_SCB_SPI_Enable(obj->base); } int spi_master_write(spi_t *obj_in, int value) { spi_obj_t *obj = OBJ_P(obj_in); MBED_ASSERT(obj->ms_mode == CY_SCB_SPI_MASTER); Cy_SCB_SPI_Write(obj->base, value); while (spi_busy(obj_in)) { /* Wait until transfer complete */ } return Cy_SCB_SPI_Read(obj->base); } int spi_master_block_write(spi_t *obj_in, const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length, char write_fill) { spi_obj_t *obj = OBJ_P(obj_in); MBED_ASSERT(obj->ms_mode == CY_SCB_SPI_MASTER); int trans_length = 0; int rx_count = 0; int tx_count = 0; uint8_t tx_byte = (uint8_t) write_fill; /* Make sure no leftovers from previous transactions */ Cy_SCB_SPI_ClearRxFifo(obj->base); /* Calculate transaction length */ trans_length = (tx_length > rx_length) ? tx_length : rx_length; /* Get first byte to transmit */ if (0 != tx_length) { tx_byte = *tx_buffer++; } /* Send required number of bytes */ while (tx_count < trans_length) { /* Put data into the TX FIFO to transmit */ if (0 != Cy_SCB_SPI_Write(obj->base, tx_byte)) { ++tx_count; /* Get next byte to transfer */ if (tx_count < tx_length) { tx_byte = *tx_buffer++; } else { tx_byte = (uint8_t) write_fill; } } /* If we have bytes to receive check the RX FIFO */ if (rx_count < rx_length) { if (Cy_SCB_SPI_GetNumInRxFifo(obj->base) > 0) { *rx_buffer++ = (char) Cy_SCB_SPI_Read(obj->base); ++rx_count; } } } /* Wait until trasnfer completion and probe RX FIFO */ while (true) { bool spi_free = (0 == spi_busy(obj_in)); if ((rx_count < rx_length) && (Cy_SCB_SPI_GetNumInRxFifo(obj->base) > 0)) { *rx_buffer++ = (char)Cy_SCB_SPI_Read(obj->base); ++rx_count; } if (spi_free) { /* Exit after try read from RX FIFO to catch last byte */ break; } } /* Clear RX FIFO (handles case when rx_length less than tx_length) */ Cy_SCB_SPI_ClearRxFifo(obj->base); return trans_length; } int spi_slave_receive(spi_t *obj_in) { spi_obj_t *obj = OBJ_P(obj_in); MBED_ASSERT(obj->ms_mode == CY_SCB_SPI_SLAVE); return Cy_SCB_SPI_GetNumInRxFifo(obj->base); } int spi_slave_read(spi_t *obj_in) { spi_obj_t *obj = OBJ_P(obj_in); MBED_ASSERT(obj->ms_mode == CY_SCB_SPI_SLAVE); while (0 == Cy_SCB_SPI_GetNumInRxFifo(obj->base)) { /* Wait for data to be read */ } return Cy_SCB_SPI_Read(obj->base); } void spi_slave_write(spi_t *obj_in, int value) { spi_obj_t *obj = OBJ_P(obj_in); MBED_ASSERT(obj->ms_mode == CY_SCB_SPI_SLAVE); while (0 == Cy_SCB_SPI_Write(obj->base, value)) { /* Wait for a place available in a FIFO to make a successful write */ } } int spi_busy(spi_t *obj_in) { spi_obj_t *obj = OBJ_P(obj_in); /* Determine end of transfer condition (MISRA Rule 12.2) */ bool busy = Cy_SCB_SPI_IsBusBusy(obj->base); bool trasmitting = !Cy_SCB_SPI_IsTxComplete(obj->base); return (busy || trasmitting); } uint8_t spi_get_module(spi_t *obj_in) { return (uint8_t) OBJ_P(obj_in)->spi_id; } const PinMap *spi_master_mosi_pinmap() { return PinMap_SPI_MOSI; } const PinMap *spi_master_miso_pinmap() { return PinMap_SPI_MISO; } const PinMap *spi_master_clk_pinmap() { return PinMap_SPI_SCLK; } const PinMap *spi_master_cs_pinmap() { return PinMap_SPI_SSEL; } const PinMap *spi_slave_mosi_pinmap() { return PinMap_SPI_MOSI; } const PinMap *spi_slave_miso_pinmap() { return PinMap_SPI_MISO; } const PinMap *spi_slave_clk_pinmap() { return PinMap_SPI_SCLK; } const PinMap *spi_slave_cs_pinmap() { return PinMap_SPI_SSEL; } #if (DEVICE_SPI_ASYNCH) void spi_master_transfer(spi_t *obj_in, const void *tx, size_t tx_length, void *rx, size_t rx_length, uint8_t bit_width, uint32_t handler, uint32_t event, DMAUsage hint) { (void) hint; /* At the moment we do not support DAM transfers, so this parameter gets ignored. */ spi_obj_t *obj = OBJ_P(obj_in); MBED_ASSERT(obj->ms_mode == CY_SCB_SPI_MASTER); if (obj->pending != PENDING_NONE) { return; } /* Validate buffer parameters */ if (((obj->data_bits <= 8) && (bit_width != 8)) || ((obj->data_bits > 8) && (bit_width != 16))) { error("SPI: buffer configurations does not match device configuration"); } /* Configure interrupt handler */ obj->handler = handler; if (spi_irq_setup_channel(obj) < 0) { return; } /* Setup transfer */ obj->events = event; if (tx_length > rx_length) { if (rx_length > 0) { /* I) write + read, II) write only */ obj->pending = PENDING_TX_RX; obj->rx_buffer = NULL; obj->tx_buffer = (bit_width == 8) ? (void *)(((uint8_t *)tx) + rx_length) : (void *)(((uint16_t *)tx) + rx_length); obj->tx_buffer_size = tx_length - rx_length; Cy_SCB_SPI_Transfer(obj->base, (void *)tx, rx, rx_length, &obj->context); } else { /* I) write only */ obj->pending = PENDING_TX; obj->rx_buffer = NULL; obj->tx_buffer = NULL; Cy_SCB_SPI_Transfer(obj->base, (void *)tx, NULL, tx_length, &obj->context); } } else if (rx_length > tx_length) { if (tx_length > 0) { /* I) write + read, II) read only */ obj->pending = PENDING_TX_RX; obj->rx_buffer = (bit_width == 8) ? (void *)(((uint8_t *)rx) + tx_length) : (void *)(((uint16_t *)rx) + tx_length); obj->rx_buffer_size = rx_length - tx_length; obj->tx_buffer = NULL; Cy_SCB_SPI_Transfer(obj->base, (void *)tx, rx, tx_length, &obj->context); } else { /* I) read only. */ obj->pending = PENDING_RX; obj->rx_buffer = NULL; obj->tx_buffer = NULL; Cy_SCB_SPI_Transfer(obj->base, NULL, rx, rx_length, &obj->context); } } else { /* RX and TX of the same size: I) write + read. */ obj->pending = PENDING_TX_RX; obj->rx_buffer = NULL; obj->tx_buffer = NULL; Cy_SCB_SPI_Transfer(obj->base, (void *)tx, rx, tx_length, &obj->context); } } uint32_t spi_irq_handler_asynch(spi_t *obj_in) { spi_obj_t *obj = OBJ_P(obj_in); uint32_t event = 0; void *buf; /* Process actual interrupt */ Cy_SCB_SPI_Interrupt(obj->base, &obj->context); if (0 != (Cy_SCB_SPI_GetTransferStatus(obj->base, &obj->context) & CY_SCB_SPI_TRANSFER_OVERFLOW)) { event = SPI_EVENT_RX_OVERFLOW; } if (0 != (Cy_SCB_SPI_GetTransferStatus(obj->base, &obj->context) & CY_SCB_SPI_SLAVE_TRANSFER_ERR)) { event |= SPI_EVENT_ERROR; } if (0 == (Cy_SCB_SPI_GetTransferStatus(obj->base, &obj->context) & CY_SCB_SPI_TRANSFER_ACTIVE)) { /* Check to see if the second transfer phase needs to be started */ MBED_ASSERT(!(obj->tx_buffer && obj->rx_buffer)); if (obj->tx_buffer) { /* Start TX transfer */ obj->pending = PENDING_TX; buf = obj->tx_buffer; obj->tx_buffer = NULL; Cy_SCB_SPI_Transfer(obj->base, buf, NULL, obj->tx_buffer_size, &obj->context); } else if (obj->rx_buffer) { /* Start RX transfer */ obj->pending = PENDING_RX; buf = obj->rx_buffer; obj->rx_buffer = NULL; Cy_SCB_SPI_Transfer(obj->base, NULL, buf, obj->rx_buffer_size, &obj->context); } else { event = SPI_EVENT_COMPLETE; obj->pending = PENDING_NONE; } } return (event & obj->events); } uint8_t spi_active(spi_t *obj_in) { spi_obj_t *obj = OBJ_P(obj_in); return (obj->pending != PENDING_NONE); } void spi_abort_asynch(spi_t *obj_in) { spi_obj_t *obj = OBJ_P(obj_in); Cy_SCB_SPI_AbortTransfer(obj->base, &obj->context); obj->pending = PENDING_NONE; } #endif /* DEVICE_ASYNCH */