blob: d1d26c5b238bfa803f3401c7fe37a3e9ed88b633 [file] [log] [blame]
/*
*
* Copyright (c) 2022 Project CHIP Authors
*
* 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 "sl_wfx_configuration_defaults.h"
#include "sl_wfx.h"
#include "sl_wfx_board.h"
#include "sl_wfx_host_api.h"
#include "dmadrv.h"
#include "em_bus.h"
#include "em_cmu.h"
#include "em_gpio.h"
#include "em_ldma.h"
#include "em_usart.h"
#include "spidrv.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "FreeRTOS.h"
#include "semphr.h"
#ifdef SLEEP_ENABLED
#include "sl_power_manager.h"
#endif
#include "AppConfig.h"
#include "gpiointerrupt.h"
#include "sl_spidrv_exp_config.h"
#include "sl_wfx_board.h"
#include "sl_wfx_host.h"
#include "sl_wfx_task.h"
#include "wfx_host_events.h"
extern SPIDRV_Handle_t sl_spidrv_exp_handle;
#define USART SL_WFX_HOST_PINOUT_SPI_PERIPHERAL
StaticSemaphore_t xEfrSpiSemaBuffer;
static SemaphoreHandle_t spi_sem;
static unsigned int tx_dma_channel;
static unsigned int rx_dma_channel;
static uint32_t dummy_rx_data;
static uint32_t dummy_tx_data;
static bool spi_enabled = false;
#if defined(EFR32MG12)
uint8_t wirq_irq_nb = SL_WFX_HOST_PINOUT_SPI_IRQ;
#elif defined(EFR32MG24)
uint8_t wirq_irq_nb = SL_WFX_HOST_PINOUT_SPI_WIRQ_PIN; // SL_WFX_HOST_PINOUT_SPI_WIRQ_PIN;
#endif
#define PIN_OUT_SET 1
#define PIN_OUT_CLEAR 0
/****************************************************************************
* @fn sl_status_t sl_wfx_host_init_bus(void)
* @brief
* Initialize SPI peripheral
* @param[in] None
* @return returns SL_STATUS_OK
*****************************************************************************/
sl_status_t sl_wfx_host_init_bus(void)
{
spi_enabled = true;
/* Assign allocated DMA channel */
tx_dma_channel = sl_spidrv_exp_handle->txDMACh;
rx_dma_channel = sl_spidrv_exp_handle->rxDMACh;
/*
* Route EUSART1 MOSI, MISO, and SCLK to the specified pins. CS is
* not controlled by EUSART so there is no write to the corresponding
* EUSARTROUTE register to do this.
*/
MY_USART->CTRL |= (1u << _USART_CTRL_SMSDELAY_SHIFT);
#if defined(EFR32MG12)
MY_USART->ROUTEPEN = USART_ROUTEPEN_TXPEN | USART_ROUTEPEN_RXPEN | USART_ROUTEPEN_CLKPEN;
#endif
#if defined(EFR32MG24)
GPIO->USARTROUTE[0].ROUTEEN = GPIO_USART_ROUTEEN_RXPEN | // MISO
GPIO_USART_ROUTEEN_TXPEN | // MOSI
GPIO_USART_ROUTEEN_CLKPEN;
#endif
spi_sem = xSemaphoreCreateBinaryStatic(&xEfrSpiSemaBuffer);
xSemaphoreGive(spi_sem);
return SL_STATUS_OK;
}
/****************************************************************************
* @fn sl_status_t sl_wfx_host_deinit_bus(void)
* @brief
* De-initialize SPI peripheral and DMAs
* @param[in] None
* @return returns SL_STATUS_OK
*****************************************************************************/
sl_status_t sl_wfx_host_deinit_bus(void)
{
vSemaphoreDelete(spi_sem);
// Stop DMAs.
DMADRV_StopTransfer(rx_dma_channel);
DMADRV_StopTransfer(tx_dma_channel);
DMADRV_FreeChannel(tx_dma_channel);
DMADRV_FreeChannel(rx_dma_channel);
DMADRV_DeInit();
USART_Reset(MY_USART);
return SL_STATUS_OK;
}
/****************************************************************************
* @fn sl_status_t sl_wfx_host_spi_cs_assert()
* @brief
* Assert chip select.
* @param[in] None
* @return returns SL_STATUS_OK
*****************************************************************************/
sl_status_t sl_wfx_host_spi_cs_assert()
{
GPIO_PinOutClear(SL_SPIDRV_EXP_CS_PORT, SL_SPIDRV_EXP_CS_PIN);
return SL_STATUS_OK;
}
/****************************************************************************
* @fn sl_status_t sl_wfx_host_spi_cs_deassert()
* @brief
* De-Assert chip select.
* @param[in] None
* @return returns SL_STATUS_OK
*****************************************************************************/
sl_status_t sl_wfx_host_spi_cs_deassert()
{
GPIO_PinOutSet(SL_SPIDRV_EXP_CS_PORT, SL_SPIDRV_EXP_CS_PIN);
return SL_STATUS_OK;
}
/****************************************************************************
* @fn static bool rx_dma_complete(unsigned int channel, unsigned int sequenceNo, void *userParam)
* @brief
* function called when the DMA complete
* @param[in] channel:
* @param[in] sequenceNo: sequence number
* @param[in] userParam: user parameter
* @return returns true if suucessful,
* false otherwise
*****************************************************************************/
static bool rx_dma_complete(unsigned int channel, unsigned int sequenceNo, void * userParam)
{
(void) channel;
(void) sequenceNo;
(void) userParam;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(spi_sem, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
return true;
}
/****************************************************************************
* @fn void receiveDMA(uint8_t *buffer, uint16_t buffer_length)
* @brief
* start receive DMA
* @param[in] buffer:
* @param[in] buffer_length:
* @return None
*****************************************************************************/
void receiveDMA(uint8_t * buffer, uint16_t buffer_length)
{
// Start receive DMA.
DMADRV_PeripheralMemory(rx_dma_channel, MY_USART_RX_SIGNAL, (void *) buffer, (void *) &(MY_USART->RXDATA), true, buffer_length,
dmadrvDataSize1, rx_dma_complete, NULL);
// Start transmit DMA.
DMADRV_MemoryPeripheral(tx_dma_channel, MY_USART_TX_SIGNAL, (void *) &(MY_USART->TXDATA), (void *) &(dummy_tx_data), false,
buffer_length, dmadrvDataSize1, NULL, NULL);
}
/****************************************************************************
* @fn void transmitDMA(uint8_t *buffer, uint16_t buffer_length)
* @brief
* start transmit DMA
* @param[in] buffer:
* @param[in] buffer_length:
* @return None
*****************************************************************************/
void transmitDMA(uint8_t * buffer, uint16_t buffer_length)
{
// Receive DMA runs only to initiate callback
// Start receive DMA.
DMADRV_PeripheralMemory(rx_dma_channel, MY_USART_RX_SIGNAL, &dummy_rx_data, (void *) &(MY_USART->RXDATA), false, buffer_length,
dmadrvDataSize1, rx_dma_complete, NULL);
// Start transmit DMA.
DMADRV_MemoryPeripheral(tx_dma_channel, MY_USART_TX_SIGNAL, (void *) &(MY_USART->TXDATA), (void *) buffer, true, buffer_length,
dmadrvDataSize1, NULL, NULL);
}
/****************************************************************************
* @fn sl_status_t sl_wfx_host_spi_transfer_no_cs_assert(sl_wfx_host_bus_transfer_type_t type,
uint8_t *header,
uint16_t header_length,
uint8_t *buffer,
uint16_t buffer_length)
* @brief
* WFX SPI transfer implementation
* @param[in] type:
* @param[in] header:
* @param[in] header_length:
* @param[in] buffer:
* @param[in] buffer_length:
* @return returns SL_STATUS_OK if successful,
* SL_STATUS_FAIL otherwise
*****************************************************************************/
sl_status_t sl_wfx_host_spi_transfer_no_cs_assert(sl_wfx_host_bus_transfer_type_t type, uint8_t * header, uint16_t header_length,
uint8_t * buffer, uint16_t buffer_length)
{
sl_status_t result = SL_STATUS_FAIL;
const bool is_read = (type == SL_WFX_BUS_READ);
while (!(MY_USART->STATUS & USART_STATUS_TXBL))
{
}
MY_USART->CMD = USART_CMD_CLEARRX | USART_CMD_CLEARTX;
/* header length should be greater than 0 */
if (header_length > 0)
{
for (uint8_t * buffer_ptr = header; header_length > 0; --header_length, ++buffer_ptr)
{
MY_USART->TXDATA = (uint32_t)(*buffer_ptr);
while (!(MY_USART->STATUS & USART_STATUS_TXC))
{
}
}
while (!(MY_USART->STATUS & USART_STATUS_TXBL))
{
}
}
/* buffer length should be greater than 0 */
if (buffer_length > 0)
{
MY_USART->CMD = USART_CMD_CLEARRX | USART_CMD_CLEARTX;
if (xSemaphoreTake(spi_sem, portMAX_DELAY) == pdTRUE)
{
if (is_read)
{
receiveDMA(buffer, buffer_length);
result = SL_STATUS_OK;
}
else
{
transmitDMA(buffer, buffer_length);
result = SL_STATUS_OK;
}
if (xSemaphoreTake(spi_sem, portMAX_DELAY) == pdTRUE)
{
xSemaphoreGive(spi_sem);
}
}
else
{
result = SL_STATUS_TIMEOUT;
}
}
return result;
}
/****************************************************************************
* @fn void sl_wfx_host_start_platform_interrupt(void)
* @brief
* Enable WFX interrupt
* @param[in] none
* @return None
*****************************************************************************/
void sl_wfx_host_start_platform_interrupt(void)
{
// Enable (and clear) the bus interrupt
GPIO_ExtIntConfig(SL_WFX_HOST_PINOUT_SPI_WIRQ_PORT, SL_WFX_HOST_PINOUT_SPI_WIRQ_PIN, wirq_irq_nb, true, false, true);
}
/****************************************************************************
* @fn sl_status_t sl_wfx_host_disable_platform_interrupt(void)
* @brief
* Disable WFX interrupt
* @param[in] None
* @return returns SL_STATUS_OK if successful,
* SL_STATUS_FAIL otherwise
*****************************************************************************/
sl_status_t sl_wfx_host_disable_platform_interrupt(void)
{
GPIO_IntDisable(1 << wirq_irq_nb);
return SL_STATUS_OK;
}
/****************************************************************************
* @fn sl_status_t sl_wfx_host_enable_platform_interrupt(void)
* @brief
* enable the platform interrupt
* @param[in] None
* @return returns SL_STATUS_OK if successful,
* SL_STATUS_FAIL otherwise
*****************************************************************************/
sl_status_t sl_wfx_host_enable_platform_interrupt(void)
{
GPIO_IntEnable(1 << wirq_irq_nb);
return SL_STATUS_OK;
}
/****************************************************************************
* @fn sl_status_t sl_wfx_host_enable_spi(void)
* @brief
* enable spi
* @param[in] None
* @return returns SL_STATUS_OK if successful,
* SL_STATUS_FAIL otherwise
*****************************************************************************/
sl_status_t sl_wfx_host_enable_spi(void)
{
if (spi_enabled == false)
{
#ifdef SLEEP_ENABLED
// Prevent the host to use lower EM than EM1
sl_power_manager_add_em_requirement(SL_POWER_MANAGER_EM1);
#endif
spi_enabled = true;
}
return SL_STATUS_OK;
}
/****************************************************************************
* @fn sl_status_t sl_wfx_host_disable_spi(void)
* @brief
* disable spi
* @param[in] None
* @return returns SL_STATUS_OK if successful,
* SL_STATUS_FAIL otherwise
*****************************************************************************/
sl_status_t sl_wfx_host_disable_spi(void)
{
if (spi_enabled == true)
{
spi_enabled = false;
#ifdef SLEEP_ENABLED
// Allow the host to use the lowest allowed EM
sl_power_manager_remove_em_requirement(SL_POWER_MANAGER_EM1);
#endif
}
return SL_STATUS_OK;
}
/*
* IRQ for SPI callback
* Clear the Interrupt and wake up the task that
* handles the actions of the interrupt (typically - wfx_bus_task ())
*/
static void sl_wfx_spi_wakeup_irq_callback(uint8_t irqNumber)
{
BaseType_t bus_task_woken;
uint32_t interrupt_mask;
if (irqNumber != wirq_irq_nb)
return;
// Get and clear all pending GPIO interrupts
interrupt_mask = GPIO_IntGet();
GPIO_IntClear(interrupt_mask);
bus_task_woken = pdFALSE;
xSemaphoreGiveFromISR(wfx_wakeup_sem, &bus_task_woken);
vTaskNotifyGiveFromISR(wfx_bus_task_handle, &bus_task_woken);
portYIELD_FROM_ISR(bus_task_woken);
}
/****************************************************************************
* Init some actions pins to the WF-200 expansion board
*****************************************************************************/
void sl_wfx_host_gpio_init(void)
{
EFR32_LOG("WIFI: GPIO Init:IRQ=%d", wirq_irq_nb);
// Enable GPIO clock.
CMU_ClockEnable(cmuClock_GPIO, true);
// Configure WF200 reset pin.
GPIO_PinModeSet(SL_WFX_HOST_PINOUT_RESET_PORT, SL_WFX_HOST_PINOUT_RESET_PIN, gpioModePushPull, 0);
// Configure WF200 WUP pin.
GPIO_PinModeSet(SL_WFX_HOST_PINOUT_WUP_PORT, SL_WFX_HOST_PINOUT_WUP_PIN, gpioModePushPull, 0);
// GPIO used as IRQ.
GPIO_PinModeSet(SL_WFX_HOST_PINOUT_SPI_WIRQ_PORT, SL_WFX_HOST_PINOUT_SPI_WIRQ_PIN, gpioModeInputPull, 0);
CMU_OscillatorEnable(cmuOsc_LFXO, true, true);
// Set up interrupt based callback function - trigger on both edges.
GPIOINT_Init();
GPIO_ExtIntConfig(SL_WFX_HOST_PINOUT_SPI_WIRQ_PORT, SL_WFX_HOST_PINOUT_SPI_WIRQ_PIN, wirq_irq_nb, true, false,
false); /* Don't enable it */
GPIOINT_CallbackRegister(wirq_irq_nb, sl_wfx_spi_wakeup_irq_callback);
// Change GPIO interrupt priority (FreeRTOS asserts unless this is done here!)
NVIC_ClearPendingIRQ(1 << wirq_irq_nb);
NVIC_SetPriority(GPIO_EVEN_IRQn, 5);
NVIC_SetPriority(GPIO_ODD_IRQn, 5);
}