mbed-os/targets/TARGET_Cypress/TARGET_PSOC6/spi_api.c

693 lines
20 KiB
C

/*
* 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(&params, 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 */