mbed-os/connectivity/drivers/wifi/TARGET_STM/COMPONENT_EMW3080B/EMW3080B_SPI.cpp

410 lines
12 KiB
C++

/*
* Copyright (c) STMicroelectronics 2021
* 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.
*/
/* Includes ------------------------------------------------------------------*/
/* Private includes ----------------------------------------------------------*/
#include "EMW3080B_SPI.h"
#define DEBUG_SILENT 0
#define DEBUG_WARNING 1
#define DEBUG_INFO 2
#define DEBUG_LOG 3
#define DEFAULT_DEBUG DEBUG_SILENT
static EMW3080B_SPI *emw3080B_spi_object;
EMW3080B_SPI::EMW3080B_SPI(bool debug,
PinName mosi,
PinName miso,
PinName sclk,
PinName nss,
PinName notify,
PinName flow,
PinName reset
): SPI(mosi, miso, sclk), _resetpin(reset), _nsspin(nss), _notifypin(notify), _flowpin(flow)
{
if (debug) {
_debug_level = DEBUG_INFO; // too much real time impact with DEBUG_LOG
} else {
_debug_level = DEFAULT_DEBUG;
}
_notify_irq = new InterruptIn(notify);
_flow_irq = new InterruptIn(flow);
_notify_irq->rise(NULL);
_flow_irq->rise(NULL);
emw3080B_spi_object = this;
/* MXWIFI supports 30 Mhz SPI clock but Mbed implementation is limitted to 5 Mhz */
/* DMA implementation would be required to increase this supported frequency */
SPI::frequency(5000000);
/* Phase 0 and One edge => mode 0 */
SPI::format(8, 0);
SPI::set_default_write_value(0);
SEM_INIT(spi_transfer_done_sem, 1);
}
int flow_rise_count = 0;
int notify_rise_count = 0;
void EMW3080B_SPI::flow_rise()
{
flow_rise_count++;
SEM_SIGNAL(spi_flow_rise_sem);
}
void EMW3080B_SPI::notify_rise()
{
notify_rise_count++;
if (SEM_SIGNAL(spi_txrx_sem) != SEM_OK) {
error("failed to signal spi_txrx_sem\n");
}
}
#pragma pack(1)
typedef struct _spi_header {
uint8_t type;
uint16_t len;
uint16_t lenx;
uint8_t dummy[3];
} spi_header_t;
#pragma pack()
/* Private define ------------------------------------------------------------*/
/* SPI protocol */
#define SPI_WRITE (0x0A)
#define SPI_READ (0x0B)
#define SPI_HEADER_SIZE (5)
#define SPI_DATA_SIZE (MX_WIFI_HCI_DATA_SIZE)
#define SPI_WAITING_FLOW_HIGH_TIMEOUT (100) /* ms unit */
#define SPI_MAX_TRANSMIT_DURATION (500) /* ms unit */
/* SPI CS */
#define MX_WIFI_SPI_CS_HIGH() _nsspin = 1
#define MX_WIFI_SPI_CS_LOW() _nsspin = 0
/**
* @brief Initialize SPI
* @param None
* @retval None
*/
int8_t EMW3080B_SPI::IO_Init(uint16_t mode)
{
int8_t ret = 0;
if (MX_WIFI_RESET == mode) {
/* HW reset */
MX_WIFI_SPI_CS_HIGH();
_resetpin = 0;
rtos::ThisThread::sleep_for(100ms);
_resetpin = 1;
rtos::ThisThread::sleep_for(1200ms);
} else {
ret = mx_wifi_spi_txrx_start();
}
return ret;
}
/**
* @brief DeInitialize SPI
* @param None
* @retval None
*/
int8_t EMW3080B_SPI::IO_DeInit(void)
{
mx_wifi_spi_txrx_stop();
return 0;
}
void EMW3080B_SPI::IO_Delay(uint32_t delayms)
{
return;
}
uint16_t EMW3080B_SPI::IO_Send(uint8_t *data, uint16_t len)
{
if ((NULL == data) || (0 == len) || (len > SPI_DATA_SIZE)) {
return 0;
}
spi_tx_data = data;
spi_tx_len = len;
if (SEM_SIGNAL(spi_txrx_sem) != SEM_OK) {
/* Happen if received thread did not have a chance to run on time, need to increase priority */
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : spi semaphore has been already notified\n");
}
return len;
}
uint16_t EMW3080B_SPI::IO_Receive(uint8_t *buffer, uint16_t buff_size)
{
return 0U;
}
int8_t EMW3080B_SPI::wait_flow_high(uint32_t timeout)
{
int8_t ret = 0;
if (SEM_WAIT(spi_flow_rise_sem, timeout, NULL) != SEM_OK) {
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : wait_flow_high semaphore failed, timeout\n");
ret = -1;
}
return ret;
}
int spi_handler_count = 0;
int spi_handler_event_value = 0;
void EMW3080B_SPI::spi_handler(int event)
{
spi_handler_count++;
spi_handler_event_value = event;
SEM_SIGNAL(spi_transfer_done_sem);
}
int32_t EMW3080B_SPI::TransmitReceive(uint8_t *txdata, uint8_t *rxdata, uint32_t datalen,
uint32_t timeout)
{
int32_t ret = 0;
debug_if(_debug_level >= DEBUG_LOG, "EMW3080B_SPI : Spi Tx Rx %" PRIu32 "\n", datalen);
SPI::transfer((const uint8_t *) txdata, (int) datalen, rxdata, (int) datalen, callback(this, &EMW3080B_SPI::spi_handler), SPI_EVENT_COMPLETE);
if (SEM_WAIT(spi_transfer_done_sem, timeout, NULL) != SEM_OK) {
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : Timeout on TransmitReceive %d\n", spi_handler_count);
ret = -1;
}
return ret;
}
int32_t EMW3080B_SPI::Transmit(uint8_t *txdata, uint32_t datalen, uint32_t timeout)
{
int32_t ret = 0;
debug_if(_debug_level >= DEBUG_LOG, "EMW3080B_SPI : Spi Tx %" PRIu32 "\n", datalen);
SPI::transfer((const uint8_t *) txdata, (int) datalen, (uint8_t *)NULL, (int) datalen, callback(this, &EMW3080B_SPI::spi_handler), SPI_EVENT_COMPLETE);
if (SEM_WAIT(spi_transfer_done_sem, timeout, NULL) != SEM_OK) {
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : Timeout on Transmit\n");
ret = -1;
}
return ret;
}
int32_t EMW3080B_SPI::Receive(uint8_t *rxdata, uint32_t datalen, uint32_t timeout)
{
int32_t ret = 0;
debug_if(_debug_level >= DEBUG_LOG, "EMW3080B_SPI : Spi Rx %" PRIu32 "\n", datalen);
SPI::transfer((const uint8_t *) NULL, (int) datalen, rxdata, (int) datalen, callback(this, &EMW3080B_SPI::spi_handler), SPI_EVENT_COMPLETE);
if (SEM_WAIT(spi_transfer_done_sem, timeout, NULL) != SEM_OK) {
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : Timeout on Receive\n");
ret = -1;
}
return ret;
}
void EMW3080B_SPI::process_txrx_poll(uint32_t timeout)
{
spi_header_t mheader, sheader;
int32_t ret;
uint8_t *txdata;
uint8_t *p = NULL;
uint16_t datalen;
static mx_buf_t *netb = NULL;
bool first_miss = true;
MX_WIFI_SPI_CS_HIGH();
while (netb == NULL) {
netb = MX_NET_BUFFER_ALLOC(MX_WIFI_BUFFER_SIZE);
if (netb == NULL) {
rtos::ThisThread::sleep_for(1ms);
if (true == first_miss) {
first_miss = false;
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : Running Out of buffer for RX\n");
}
}
}
/* waiting for data to be sent or to be received */
if (SEM_WAIT(spi_txrx_sem, timeout, NULL) == SEM_OK) {
if (spi_tx_data == NULL) {
if (_notifypin == 0) {
/* tx data null means no data to send , _flowpin low means no data to received */
error("EMW3080B_SPI : Nothing to process but wake UP!!!\n");
}
txdata = NULL;
mheader.len = 0;
} else {
mheader.len = spi_tx_len;
txdata = spi_tx_data;
}
mheader.type = SPI_WRITE;
mheader.lenx = ~mheader.len;
mheader.dummy[0] = 0;
mheader.dummy[1] = 0;
mheader.dummy[2] = 0;
MX_WIFI_SPI_CS_LOW();
/* wait EMW to be ready */
if (wait_flow_high(SPI_WAITING_FLOW_HIGH_TIMEOUT) != 0) {
MX_WIFI_SPI_CS_HIGH();
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : wait flow high timeout, notify_rise_count %d flow_rise_count %d flow is %d\n", notify_rise_count, flow_rise_count, (int)_flowpin);
}
/* transmit only header part */
sheader.type = 0;
sheader.len = 0;
if (TransmitReceive((uint8_t *)&mheader, (uint8_t *)&sheader, sizeof(mheader), SPI_MAX_TRANSMIT_DURATION)) {
MX_WIFI_SPI_CS_HIGH();
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : Send mheader error\r\n");
}
if (sheader.type != SPI_READ) {
MX_WIFI_SPI_CS_HIGH();
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : Invalid SPI type %02x\r\n", sheader.type);
}
if ((sheader.len ^ sheader.lenx) != 0xFFFF) {
MX_WIFI_SPI_CS_HIGH();
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : Invalid len %04x-%04x\r\n", sheader.len, sheader.lenx);
}
/* send or received header must be not null */
if ((sheader.len == 0) && (mheader.len == 0)) {
MX_WIFI_SPI_CS_HIGH();
return;
}
if ((sheader.len > SPI_DATA_SIZE) || (mheader.len > SPI_DATA_SIZE)) {
MX_WIFI_SPI_CS_HIGH();
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : SPI length invalid: %d-%d\r\n", sheader.len, mheader.len);
}
/* keep max length */
if (mheader.len > sheader.len) {
datalen = mheader.len;
} else {
datalen = sheader.len;
}
/* allocate a buffer */
if (sheader.len > 0) {
/* get payload */
p = MX_NET_BUFFER_PAYLOAD(netb);
} else {
p = NULL;
}
/* flow must be high */
if (wait_flow_high(SPI_WAITING_FLOW_HIGH_TIMEOUT) != 0) {
MX_WIFI_SPI_CS_HIGH();
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : timeout waiting for flow high after transfer !\r\n");
}
/* transmit and received */
if (NULL != txdata) {
spi_tx_data = NULL;
spi_tx_len = 0;
if (NULL != p) {
ret = TransmitReceive(txdata, p, datalen, SPI_MAX_TRANSMIT_DURATION);
} else {
ret = Transmit(txdata, datalen, SPI_MAX_TRANSMIT_DURATION);
}
} else {
ret = Receive(p, datalen, SPI_MAX_TRANSMIT_DURATION);
}
if (ret) {
MX_WIFI_SPI_CS_HIGH();
debug_if(_debug_level >= DEBUG_WARNING, "EMW3080B_SPI : Transmit/Receive data timeout\r\n");
}
/* resize the input buffer and sent it back to processing thread */
if (sheader.len > 0) {
MX_NET_BUFFER_SET_PAYLOAD_SIZE(netb, sheader.len);
mx_wifi_hci_input(netb);
netb = NULL;
}
MX_WIFI_SPI_CS_HIGH();
}
}
void mx_wifi_spi_txrx_task(void)
{
while (1) {
emw3080B_spi_object->process_txrx_poll(WAIT_FOREVER);
}
}
int8_t EMW3080B_SPI::mx_wifi_spi_txrx_start(void)
{
int8_t ret = 0;
SEM_INIT(spi_txrx_sem, 2);
SEM_INIT(spi_flow_rise_sem, 1);
SEM_INIT(spi_transfer_done_sem, 1);
_notify_irq->rise(callback(this, &EMW3080B_SPI::notify_rise));
_flow_irq->rise(callback(this, &EMW3080B_SPI::flow_rise));
if (THREAD_OK == THREAD_INIT(MX_WIFI_TxRxThreadId,
mx_wifi_spi_txrx_task, NULL,
MX_WIFI_SPI_THREAD_STACK_SIZE,
MX_WIFI_SPI_THREAD_PRIORITY)) {
ret = 0;
} else {
ret = -1;
}
return ret;
}
int8_t EMW3080B_SPI::mx_wifi_spi_txrx_stop(void)
{
THREAD_DEINIT(MX_WIFI_TxRxThreadId);
_notify_irq->rise(NULL);
_flow_irq->rise(NULL);
SEM_DEINIT(spi_txrx_sem);
SEM_DEINIT(spi_flow_rise_sem);
SEM_DEINIT(spi_transfer_done_sem);
return 0;
}