926 lines
27 KiB
C
926 lines
27 KiB
C
/* SPDX-License-Identifier: Apache-2.0 */
|
|
/* Copyright 2015-2025 Espressif Systems (Shanghai) PTE LTD */
|
|
|
|
#include "sdkconfig.h"
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include "soc/gpio_reg.h"
|
|
#include "esp_log.h"
|
|
#include "interface.h"
|
|
#include "driver/spi_slave.h"
|
|
#include "driver/gpio.h"
|
|
#include "endian.h"
|
|
#include "freertos/task.h"
|
|
#include "mempool.h"
|
|
#include "stats.h"
|
|
#include "esp_timer.h"
|
|
#include "esp_hosted_interface.h"
|
|
#include "esp_hosted_transport.h"
|
|
#include "esp_hosted_transport_init.h"
|
|
#include "esp_hosted_header.h"
|
|
#include "host_power_save.h"
|
|
#include "esp_hosted_interface.h"
|
|
#include "slave_wifi_config.h"
|
|
#include "esp_hosted_coprocessor_fw_ver.h"
|
|
|
|
static const char TAG[] = "SPI_DRIVER";
|
|
/* SPI settings */
|
|
#define SPI_BITS_PER_WORD 8
|
|
|
|
#define ESP_SPI_MODE CONFIG_ESP_SPI_MODE
|
|
#define GPIO_MOSI CONFIG_ESP_SPI_GPIO_MOSI
|
|
#define GPIO_MISO CONFIG_ESP_SPI_GPIO_MISO
|
|
#define GPIO_SCLK CONFIG_ESP_SPI_GPIO_CLK
|
|
#define GPIO_CS CONFIG_ESP_SPI_GPIO_CS
|
|
#define GPIO_DATA_READY CONFIG_ESP_SPI_GPIO_DATA_READY
|
|
#define GPIO_HANDSHAKE CONFIG_ESP_SPI_GPIO_HANDSHAKE
|
|
|
|
#define ESP_SPI_CONTROLLER CONFIG_ESP_SPI_CONTROLLER
|
|
|
|
#define SPI_RX_QUEUE_SIZE CONFIG_ESP_SPI_RX_Q_SIZE
|
|
#define SPI_TX_QUEUE_SIZE CONFIG_ESP_SPI_TX_Q_SIZE
|
|
|
|
// de-assert HS signal on CS, instead of at end of transaction
|
|
#if defined(CONFIG_ESP_SPI_DEASSERT_HS_ON_CS)
|
|
#define HS_DEASSERT_ON_CS (1)
|
|
#else
|
|
#define HS_DEASSERT_ON_CS (0)
|
|
#endif
|
|
|
|
/* By default both Handshake and Data Ready used Active High,
|
|
* unless configured otherwise.
|
|
* For Active low, set value as 0 */
|
|
#define H_HANDSHAKE_ACTIVE_HIGH 1
|
|
#define H_DATAREADY_ACTIVE_HIGH 1
|
|
|
|
/* SPI-DMA settings */
|
|
#define SPI_DMA_ALIGNMENT_BYTES 4
|
|
#define SPI_DMA_ALIGNMENT_MASK (SPI_DMA_ALIGNMENT_BYTES-1)
|
|
#define IS_SPI_DMA_ALIGNED(VAL) (!((VAL)& SPI_DMA_ALIGNMENT_MASK))
|
|
#define MAKE_SPI_DMA_ALIGNED(VAL) (VAL += SPI_DMA_ALIGNMENT_BYTES - \
|
|
((VAL)& SPI_DMA_ALIGNMENT_MASK))
|
|
|
|
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S2)
|
|
#define DMA_CHAN ESP_SPI_CONTROLLER
|
|
#else
|
|
#define DMA_CHAN SPI_DMA_CH_AUTO
|
|
#endif
|
|
static uint8_t hosted_constructs_init_done = 0;
|
|
|
|
|
|
#if ESP_SPI_MODE==0
|
|
# error "SPI mode 0 at SLAVE is NOT supported"
|
|
#endif
|
|
/* SPI internal configs */
|
|
#define SPI_BUFFER_SIZE MAX_TRANSPORT_BUF_SIZE
|
|
#define SPI_QUEUE_SIZE 3
|
|
|
|
#define GPIO_MASK_DATA_READY (1ULL << GPIO_DATA_READY)
|
|
#define GPIO_MASK_HANDSHAKE (1ULL << GPIO_HANDSHAKE)
|
|
|
|
#if HS_DEASSERT_ON_CS
|
|
#define H_CS_INTR_TO_CLEAR_HS GPIO_INTR_ANYEDGE
|
|
#else
|
|
#define H_CS_INTR_TO_CLEAR_HS GPIO_INTR_NEGEDGE
|
|
#endif
|
|
|
|
#if H_HANDSHAKE_ACTIVE_HIGH
|
|
#define H_HS_VAL_ACTIVE GPIO_OUT_W1TS_REG
|
|
#define H_HS_VAL_INACTIVE GPIO_OUT_W1TC_REG
|
|
#define H_HS_PULL_REGISTER GPIO_PULLDOWN_ONLY
|
|
#else
|
|
#define H_HS_VAL_ACTIVE GPIO_OUT_W1TC_REG
|
|
#define H_HS_VAL_INACTIVE GPIO_OUT_W1TS_REG
|
|
#define H_HS_PULL_REGISTER GPIO_PULLUP_ONLY
|
|
#endif
|
|
|
|
#if H_DATAREADY_ACTIVE_HIGH
|
|
#define H_DR_VAL_ACTIVE GPIO_OUT_W1TS_REG
|
|
#define H_DR_VAL_INACTIVE GPIO_OUT_W1TC_REG
|
|
#define H_DR_PULL_REGISTER GPIO_PULLDOWN_ONLY
|
|
#else
|
|
#define H_DR_VAL_ACTIVE GPIO_OUT_W1TC_REG
|
|
#define H_DR_VAL_INACTIVE GPIO_OUT_W1TS_REG
|
|
#define H_DR_PULL_REGISTER GPIO_PULLUP_ONLY
|
|
#endif
|
|
|
|
static interface_context_t context;
|
|
static interface_handle_t if_handle_g;
|
|
static SemaphoreHandle_t spi_tx_sem;
|
|
static SemaphoreHandle_t spi_rx_sem;
|
|
#if HS_DEASSERT_ON_CS
|
|
static SemaphoreHandle_t wait_cs_deassert_sem;
|
|
#endif
|
|
static QueueHandle_t spi_rx_queue[MAX_PRIORITY_QUEUES];
|
|
static QueueHandle_t spi_tx_queue[MAX_PRIORITY_QUEUES];
|
|
|
|
static interface_handle_t * esp_spi_init(void);
|
|
static int32_t esp_spi_write(interface_handle_t *handle,
|
|
interface_buffer_handle_t *buf_handle);
|
|
static int esp_spi_read(interface_handle_t *if_handle, interface_buffer_handle_t * buf_handle);
|
|
static esp_err_t esp_spi_reset(interface_handle_t *handle);
|
|
static void esp_spi_deinit(interface_handle_t *handle);
|
|
static void esp_spi_read_done(void *handle);
|
|
static void queue_next_transaction(void);
|
|
|
|
if_ops_t if_ops = {
|
|
.init = esp_spi_init,
|
|
.write = esp_spi_write,
|
|
.read = esp_spi_read,
|
|
.reset = esp_spi_reset,
|
|
.deinit = esp_spi_deinit,
|
|
};
|
|
|
|
#define SPI_MEMPOOL_NUM_BLOCKS ((SPI_TX_QUEUE_SIZE+SPI_RX_QUEUE_SIZE)+SPI_QUEUE_SIZE*2)
|
|
static struct hosted_mempool * buf_mp_tx_g;
|
|
static struct hosted_mempool * buf_mp_rx_g;
|
|
static struct hosted_mempool * trans_mp_g;
|
|
|
|
static inline void spi_mempool_create(void)
|
|
{
|
|
buf_mp_tx_g = hosted_mempool_create(NULL, 0,
|
|
SPI_MEMPOOL_NUM_BLOCKS, SPI_BUFFER_SIZE);
|
|
/* re-use the mempool, as same size, can be seperate, if needed */
|
|
buf_mp_rx_g = buf_mp_tx_g;
|
|
trans_mp_g = hosted_mempool_create(NULL, 0,
|
|
SPI_MEMPOOL_NUM_BLOCKS, sizeof(spi_slave_transaction_t));
|
|
#if CONFIG_ESP_CACHE_MALLOC
|
|
assert(buf_mp_tx_g);
|
|
assert(buf_mp_rx_g);
|
|
assert(trans_mp_g);
|
|
#else
|
|
ESP_LOGI(TAG, "Using dynamic heap for mem alloc");
|
|
#endif
|
|
}
|
|
|
|
static inline void spi_mempool_destroy(void)
|
|
{
|
|
hosted_mempool_destroy(buf_mp_tx_g);
|
|
hosted_mempool_destroy(trans_mp_g);
|
|
}
|
|
|
|
static inline void *spi_buffer_tx_alloc(uint need_memset)
|
|
{
|
|
return hosted_mempool_alloc(buf_mp_tx_g, SPI_BUFFER_SIZE, need_memset);
|
|
}
|
|
|
|
static inline void *spi_buffer_rx_alloc(uint need_memset)
|
|
{
|
|
return hosted_mempool_alloc(buf_mp_rx_g, SPI_BUFFER_SIZE, need_memset);
|
|
}
|
|
|
|
static inline spi_slave_transaction_t *spi_trans_alloc(uint need_memset)
|
|
{
|
|
return hosted_mempool_alloc(trans_mp_g, sizeof(spi_slave_transaction_t), need_memset);
|
|
}
|
|
|
|
static inline void spi_buffer_tx_free(void *buf)
|
|
{
|
|
hosted_mempool_free(buf_mp_tx_g, buf);
|
|
}
|
|
|
|
static inline void spi_buffer_rx_free(void *buf)
|
|
{
|
|
hosted_mempool_free(buf_mp_rx_g, buf);
|
|
}
|
|
|
|
static inline void spi_trans_free(spi_slave_transaction_t *trans)
|
|
{
|
|
hosted_mempool_free(trans_mp_g, trans);
|
|
}
|
|
volatile uint8_t data_ready_flag = 0;
|
|
#define set_handshake_gpio() ESP_EARLY_LOGD(TAG, "+ set handshake gpio");gpio_set_level(GPIO_HANDSHAKE, 1);
|
|
#define reset_handshake_gpio() ESP_EARLY_LOGD(TAG, "- reset handshake gpio");gpio_set_level(GPIO_HANDSHAKE, 0);
|
|
#define set_dataready_gpio() if (!data_ready_flag) {ESP_EARLY_LOGD(TAG, "+ set dataready gpio");gpio_set_level(GPIO_DATA_READY, 1);data_ready_flag = 1;}
|
|
#define reset_dataready_gpio() if (data_ready_flag) {ESP_EARLY_LOGD(TAG, "- reset dataready gpio");gpio_set_level(GPIO_DATA_READY, 0);data_ready_flag = 0;}
|
|
|
|
interface_context_t *interface_insert_driver(int (*event_handler)(uint8_t val))
|
|
{
|
|
ESP_LOGI(TAG, "Using SPI interface");
|
|
memset(&context, 0, sizeof(context));
|
|
|
|
context.type = SPI;
|
|
context.if_ops = &if_ops;
|
|
context.event_handler = event_handler;
|
|
|
|
return &context;
|
|
}
|
|
|
|
int interface_remove_driver()
|
|
{
|
|
memset(&context, 0, sizeof(context));
|
|
return 0;
|
|
}
|
|
|
|
|
|
static inline int find_wifi_tx_throttling_to_be_set(void)
|
|
{
|
|
uint16_t queue_load;
|
|
uint8_t load_percent;
|
|
|
|
if (!slv_cfg_g.throttle_high_threshold) {
|
|
/* No high threshold set, no throttlling */
|
|
return 0;
|
|
}
|
|
|
|
queue_load = uxQueueMessagesWaiting(spi_rx_queue[PRIO_Q_OTHERS]);
|
|
|
|
load_percent = (queue_load*100/SPI_RX_QUEUE_SIZE);
|
|
|
|
if (load_percent > slv_cfg_g.throttle_high_threshold) {
|
|
slv_state_g.current_throttling = 1;
|
|
ESP_LOGV(TAG, "throttling started");
|
|
#if ESP_PKT_STATS
|
|
pkt_stats.sta_flowctrl_on++;
|
|
#endif
|
|
}
|
|
|
|
if (load_percent < slv_cfg_g.throttle_low_threshold) {
|
|
slv_state_g.current_throttling = 0;
|
|
ESP_LOGV(TAG, "throttling stopped");
|
|
#if ESP_PKT_STATS
|
|
pkt_stats.sta_flowctrl_off++;
|
|
#endif
|
|
}
|
|
|
|
return slv_state_g.current_throttling;
|
|
}
|
|
|
|
|
|
void generate_startup_event(uint8_t cap, uint32_t ext_cap)
|
|
{
|
|
struct esp_payload_header *header = NULL;
|
|
interface_buffer_handle_t buf_handle = {0};
|
|
struct esp_priv_event *event = NULL;
|
|
uint8_t *pos = NULL;
|
|
uint16_t len = 0;
|
|
uint8_t raw_tp_cap = 0;
|
|
uint32_t total_len = 0;
|
|
|
|
buf_handle.payload = spi_buffer_tx_alloc(MEMSET_REQUIRED);
|
|
|
|
raw_tp_cap = debug_get_raw_tp_conf();
|
|
|
|
assert(buf_handle.payload);
|
|
header = (struct esp_payload_header *) buf_handle.payload;
|
|
|
|
header->if_type = ESP_PRIV_IF;
|
|
header->if_num = 0;
|
|
header->offset = htole16(sizeof(struct esp_payload_header));
|
|
header->priv_pkt_type = ESP_PACKET_TYPE_EVENT;
|
|
header->throttle_cmd = 0;
|
|
|
|
/* Populate event data */
|
|
event = (struct esp_priv_event *) (buf_handle.payload + sizeof(struct esp_payload_header));
|
|
|
|
event->event_type = ESP_PRIV_EVENT_INIT;
|
|
|
|
/* Populate TLVs for event */
|
|
pos = event->event_data;
|
|
|
|
/* TLVs start */
|
|
|
|
/* TLV - Board type */
|
|
ESP_LOGI(TAG, "Slave chip Id[%x]", ESP_PRIV_FIRMWARE_CHIP_ID);
|
|
*pos = ESP_PRIV_FIRMWARE_CHIP_ID; pos++;len++;
|
|
*pos = LENGTH_1_BYTE; pos++;len++;
|
|
*pos = CONFIG_IDF_FIRMWARE_CHIP_ID; pos++;len++;
|
|
|
|
/* TLV - Capability */
|
|
*pos = ESP_PRIV_CAPABILITY; pos++;len++;
|
|
*pos = LENGTH_1_BYTE; pos++;len++;
|
|
*pos = cap; pos++;len++;
|
|
|
|
*pos = ESP_PRIV_TEST_RAW_TP; pos++;len++;
|
|
*pos = LENGTH_1_BYTE; pos++;len++;
|
|
*pos = raw_tp_cap; pos++;len++;
|
|
|
|
*pos = ESP_PRIV_RX_Q_SIZE; pos++;len++;
|
|
*pos = LENGTH_1_BYTE; pos++;len++;
|
|
*pos = SPI_RX_QUEUE_SIZE; pos++;len++;
|
|
|
|
*pos = ESP_PRIV_TX_Q_SIZE; pos++;len++;
|
|
*pos = LENGTH_1_BYTE; pos++;len++;
|
|
*pos = SPI_TX_QUEUE_SIZE; pos++;len++;
|
|
|
|
// convert fw version into a uint32_t
|
|
uint32_t fw_version = ESP_HOSTED_VERSION_VAL(PROJECT_VERSION_MAJOR_1,
|
|
PROJECT_VERSION_MINOR_1,
|
|
PROJECT_VERSION_PATCH_1);
|
|
|
|
// send fw version as a little-endian uint32_t
|
|
*pos = ESP_PRIV_FIRMWARE_VERSION; pos++;len++;
|
|
*pos = LENGTH_4_BYTE; pos++;len++;
|
|
// send fw_version as a little endian 32bit value
|
|
*pos = (fw_version & 0xff); pos++;len++;
|
|
*pos = (fw_version >> 8) & 0xff; pos++;len++;
|
|
*pos = (fw_version >> 16) & 0xff; pos++;len++;
|
|
*pos = (fw_version >> 24) & 0xff; pos++;len++;
|
|
|
|
/* TLVs end */
|
|
|
|
event->event_len = len;
|
|
|
|
/* payload len = Event len + sizeof(event type) + sizeof(event len) */
|
|
len += 2;
|
|
header->len = htole16(len);
|
|
|
|
total_len = len + sizeof(struct esp_payload_header);
|
|
|
|
if (!IS_SPI_DMA_ALIGNED(total_len)) {
|
|
MAKE_SPI_DMA_ALIGNED(total_len);
|
|
}
|
|
|
|
buf_handle.payload_len = total_len;
|
|
|
|
#if CONFIG_ESP_SPI_CHECKSUM
|
|
header->checksum = htole16(compute_checksum(buf_handle.payload, len + sizeof(struct esp_payload_header)));
|
|
#endif
|
|
|
|
xQueueSend(spi_tx_queue[PRIO_Q_OTHERS], &buf_handle, portMAX_DELAY);
|
|
xSemaphoreGive(spi_tx_sem);
|
|
|
|
set_dataready_gpio();
|
|
/* process first data packet here to start transactions */
|
|
queue_next_transaction();
|
|
}
|
|
|
|
|
|
/* Invoked after transaction is queued and ready for pickup by master */
|
|
static void IRAM_ATTR spi_post_setup_cb(spi_slave_transaction_t *trans)
|
|
{
|
|
/* ESP peripheral ready for spi transaction. Set hadnshake line high. */
|
|
set_handshake_gpio();
|
|
}
|
|
|
|
/* Invoked after transaction is sent/received.
|
|
* Use this to set the handshake line low */
|
|
static void IRAM_ATTR spi_post_trans_cb(spi_slave_transaction_t *trans)
|
|
{
|
|
#if !HS_DEASSERT_ON_CS
|
|
/* Clear handshake line */
|
|
reset_handshake_gpio();
|
|
#endif
|
|
}
|
|
|
|
static uint8_t * get_next_tx_buffer(uint32_t *len)
|
|
{
|
|
interface_buffer_handle_t buf_handle = {0};
|
|
esp_err_t ret = ESP_OK;
|
|
uint8_t *sendbuf = NULL;
|
|
struct esp_payload_header *header = NULL;
|
|
|
|
/* Get or create new tx_buffer
|
|
* 1. Check if SPI TX queue has pending buffers. Return if valid buffer is obtained.
|
|
* 2. Create a new empty tx buffer and return */
|
|
|
|
/* Get buffer from SPI Tx queue */
|
|
ret = xSemaphoreTake(spi_tx_sem, 0);
|
|
if (pdTRUE == ret)
|
|
if (pdFALSE == xQueueReceive(spi_tx_queue[PRIO_Q_SERIAL], &buf_handle, 0))
|
|
if (pdFALSE == xQueueReceive(spi_tx_queue[PRIO_Q_BT], &buf_handle, 0))
|
|
if (pdFALSE == xQueueReceive(spi_tx_queue[PRIO_Q_OTHERS], &buf_handle, 0))
|
|
ret = pdFALSE;
|
|
|
|
if (ret == pdTRUE && buf_handle.payload) {
|
|
if (len) {
|
|
#if ESP_PKT_STATS
|
|
if (buf_handle.if_type == ESP_SERIAL_IF)
|
|
pkt_stats.serial_tx_total++;
|
|
#endif
|
|
*len = buf_handle.payload_len;
|
|
}
|
|
/* Return real data buffer from queue */
|
|
return buf_handle.payload;
|
|
}
|
|
|
|
/* No real data pending, clear ready line and indicate host an idle state */
|
|
reset_dataready_gpio();
|
|
|
|
/* Create empty dummy buffer */
|
|
sendbuf = spi_buffer_tx_alloc(MEMSET_REQUIRED);
|
|
if (!sendbuf) {
|
|
ESP_LOGE(TAG, "Failed to allocate memory for dummy transaction");
|
|
if (len)
|
|
*len = 0;
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialize header */
|
|
header = (struct esp_payload_header *) sendbuf;
|
|
|
|
/* Populate header to indicate it as a dummy buffer */
|
|
header->if_type = ESP_MAX_IF;
|
|
header->if_num = 0xF;
|
|
header->len = 0;
|
|
header->throttle_cmd = find_wifi_tx_throttling_to_be_set();
|
|
|
|
if (len)
|
|
*len = 0;
|
|
|
|
return sendbuf;
|
|
}
|
|
|
|
static int process_spi_rx(interface_buffer_handle_t *buf_handle)
|
|
{
|
|
struct esp_payload_header *header = NULL;
|
|
uint16_t len = 0, offset = 0;
|
|
uint8_t flags = 0;
|
|
#if CONFIG_ESP_SPI_CHECKSUM
|
|
uint16_t rx_checksum = 0, checksum = 0;
|
|
#endif
|
|
|
|
/* Validate received buffer. Drop invalid buffer. */
|
|
|
|
if (!buf_handle || !buf_handle->payload) {
|
|
ESP_LOGE(TAG, "%s: Invalid params", __func__);
|
|
return -1;
|
|
}
|
|
|
|
header = (struct esp_payload_header *) buf_handle->payload;
|
|
len = le16toh(header->len);
|
|
offset = le16toh(header->offset);
|
|
flags = header->flags;
|
|
ESP_LOGV(TAG, "process_spi_rx: flags[%u]", flags);
|
|
|
|
if (flags & FLAG_POWER_SAVE_STARTED) {
|
|
ESP_LOGI(TAG, "Host informed starting to power sleep");
|
|
if (context.event_handler) {
|
|
context.event_handler(ESP_POWER_SAVE_ON);
|
|
}
|
|
} else if (flags & FLAG_POWER_SAVE_STOPPED) {
|
|
ESP_LOGI(TAG, "Host informed that it waken up");
|
|
if (context.event_handler) {
|
|
context.event_handler(ESP_POWER_SAVE_OFF);
|
|
}
|
|
}
|
|
|
|
if (!len) {
|
|
ESP_LOGV(TAG, "rx_pkt len[%u] is 0, dropping it", len);
|
|
return -1;
|
|
}
|
|
|
|
if ((len+offset) > SPI_BUFFER_SIZE) {
|
|
ESP_LOGE(TAG, "rx_pkt len+offset[%u]>max[%u], dropping it", len+offset, SPI_BUFFER_SIZE);
|
|
|
|
return -1;
|
|
}
|
|
|
|
#if CONFIG_ESP_SPI_CHECKSUM
|
|
rx_checksum = le16toh(header->checksum);
|
|
header->checksum = 0;
|
|
|
|
checksum = compute_checksum(buf_handle->payload, len+offset);
|
|
|
|
if (checksum != rx_checksum) {
|
|
ESP_LOGE(TAG, "%s: cal_chksum[%u] != exp_chksum[%u], drop len[%u] offset[%u]",
|
|
__func__, checksum, rx_checksum, len, offset);
|
|
return -1;
|
|
}
|
|
#endif
|
|
ESP_HEXLOGD("spi_rx", header, len, 32);
|
|
|
|
/* Buffer is valid */
|
|
buf_handle->if_type = header->if_type;
|
|
buf_handle->if_num = header->if_num;
|
|
buf_handle->free_buf_handle = esp_spi_read_done;
|
|
buf_handle->payload_len = len + offset;
|
|
buf_handle->priv_buffer_handle = buf_handle->payload;
|
|
|
|
|
|
#if ESP_PKT_STATS
|
|
if (buf_handle->if_type == ESP_STA_IF)
|
|
pkt_stats.hs_bus_sta_in++;
|
|
#endif
|
|
if (header->if_type == ESP_SERIAL_IF) {
|
|
xQueueSend(spi_rx_queue[PRIO_Q_SERIAL], buf_handle, portMAX_DELAY);
|
|
} else if (header->if_type == ESP_HCI_IF) {
|
|
xQueueSend(spi_rx_queue[PRIO_Q_BT], buf_handle, portMAX_DELAY);
|
|
} else {
|
|
xQueueSend(spi_rx_queue[PRIO_Q_OTHERS], buf_handle, portMAX_DELAY);
|
|
}
|
|
|
|
xSemaphoreGive(spi_rx_sem);
|
|
return 0;
|
|
}
|
|
|
|
static void queue_next_transaction(void)
|
|
{
|
|
spi_slave_transaction_t *spi_trans = NULL;
|
|
uint32_t len = 0;
|
|
uint8_t *tx_buffer = get_next_tx_buffer(&len);
|
|
if (unlikely(!tx_buffer)) {
|
|
/* Queue next transaction failed */
|
|
ESP_LOGE(TAG , "Failed to queue new transaction\r\n");
|
|
return;
|
|
}
|
|
ESP_HEXLOGD("spi_tx", tx_buffer, len, 32);
|
|
|
|
spi_trans = spi_trans_alloc(MEMSET_REQUIRED);
|
|
if (unlikely(!spi_trans)) {
|
|
assert(spi_trans);
|
|
}
|
|
|
|
/* Attach Rx Buffer */
|
|
spi_trans->rx_buffer = spi_buffer_rx_alloc(MEMSET_REQUIRED);
|
|
if (unlikely(!spi_trans->rx_buffer)) {
|
|
assert(spi_trans->rx_buffer);
|
|
}
|
|
|
|
/* Attach Tx Buffer */
|
|
spi_trans->tx_buffer = tx_buffer;
|
|
|
|
/* Transaction len */
|
|
spi_trans->length = SPI_BUFFER_SIZE * SPI_BITS_PER_WORD;
|
|
|
|
spi_slave_queue_trans(ESP_SPI_CONTROLLER, spi_trans, portMAX_DELAY);
|
|
}
|
|
|
|
static void spi_transaction_post_process_task(void* pvParameters)
|
|
{
|
|
spi_slave_transaction_t *spi_trans = NULL;
|
|
esp_err_t ret = ESP_OK;
|
|
interface_buffer_handle_t rx_buf_handle;
|
|
|
|
ESP_LOGI(TAG, "SPI post process task started");
|
|
for (;;) {
|
|
/* Check if interface is being deinitialized */
|
|
#if H_PS_UNLOAD_BUS_WHILE_PS
|
|
if (if_handle_g.state == DEINIT) {
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
ESP_LOGI(TAG, "spi deinit");
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
memset(&rx_buf_handle, 0, sizeof(rx_buf_handle));
|
|
|
|
/* Await transmission result, after any kind of transmission a new packet
|
|
* (dummy or real) must be placed in SPI slave
|
|
*/
|
|
ESP_ERROR_CHECK(spi_slave_get_trans_result(ESP_SPI_CONTROLLER, &spi_trans,
|
|
portMAX_DELAY));
|
|
|
|
#if HS_DEASSERT_ON_CS
|
|
/* Wait until CS has been deasserted before we queue a new transaction.
|
|
*
|
|
* Some MCUs delay deasserting CS at the end of a transaction.
|
|
* If we queue a new transaction without waiting for CS to deassert,
|
|
* the slave SPI can start (since CS is still asserted), and data is lost
|
|
* as host is not expecting any data.
|
|
*/
|
|
xSemaphoreTake(wait_cs_deassert_sem, portMAX_DELAY);
|
|
#endif
|
|
/* Queue new transaction to get ready as soon as possible */
|
|
queue_next_transaction();
|
|
assert(spi_trans);
|
|
#if ESP_PKT_STATS
|
|
struct esp_payload_header *header =
|
|
(struct esp_payload_header *)spi_trans->tx_buffer;
|
|
if (header->if_type == ESP_STA_IF)
|
|
pkt_stats.sta_sh_out++;
|
|
#endif
|
|
|
|
/* Free any tx buffer, data is not relevant anymore */
|
|
spi_buffer_tx_free((void *)spi_trans->tx_buffer);
|
|
/* Process received data */
|
|
if (likely(spi_trans->rx_buffer)) {
|
|
rx_buf_handle.payload = spi_trans->rx_buffer;
|
|
|
|
ret = process_spi_rx(&rx_buf_handle);
|
|
|
|
/* free rx_buffer if process_spi_rx returns an error
|
|
* In success case it will be freed later */
|
|
if (unlikely(ret)) {
|
|
spi_buffer_rx_free((void *)spi_trans->rx_buffer);
|
|
}
|
|
} else {
|
|
ESP_LOGI(TAG, "no rx_buf");
|
|
}
|
|
|
|
/* Free Transfer structure */
|
|
spi_trans_free(spi_trans);
|
|
}
|
|
}
|
|
|
|
static void IRAM_ATTR gpio_disable_hs_isr_handler(void* arg)
|
|
{
|
|
#if HS_DEASSERT_ON_CS
|
|
int level = gpio_get_level(GPIO_CS);
|
|
if (level == 0) {
|
|
/* CS is asserted, disable HS */
|
|
reset_handshake_gpio();
|
|
} else {
|
|
/* Last transaction complete, populate next one */
|
|
if (wait_cs_deassert_sem)
|
|
xSemaphoreGive(wait_cs_deassert_sem);
|
|
}
|
|
#else
|
|
reset_handshake_gpio();
|
|
#endif
|
|
}
|
|
|
|
static void register_hs_disable_pin(uint32_t gpio_num)
|
|
{
|
|
if (gpio_num != -1) {
|
|
gpio_reset_pin(gpio_num);
|
|
|
|
gpio_config_t slave_disable_hs_pin_conf={
|
|
.intr_type=GPIO_INTR_DISABLE,
|
|
.mode=GPIO_MODE_INPUT,
|
|
.pin_bit_mask=(1ULL<<gpio_num)
|
|
};
|
|
slave_disable_hs_pin_conf.pull_up_en = 1;
|
|
gpio_config(&slave_disable_hs_pin_conf);
|
|
gpio_set_intr_type(gpio_num, H_CS_INTR_TO_CLEAR_HS);
|
|
gpio_install_isr_service(0);
|
|
gpio_isr_handler_add(gpio_num, gpio_disable_hs_isr_handler, NULL);
|
|
}
|
|
}
|
|
|
|
static interface_handle_t * esp_spi_init(void)
|
|
{
|
|
if (unlikely(if_handle_g.state >= DEACTIVE)) {
|
|
return &if_handle_g;
|
|
}
|
|
|
|
#if H_PS_UNLOAD_BUS_WHILE_PS
|
|
if (hosted_constructs_init_done) {
|
|
|
|
#if H_IF_AVAILABLE_SPI_SLAVE_ENABLE_DISABLE
|
|
spi_slave_enable(ESP_SPI_CONTROLLER);
|
|
#endif
|
|
if_handle_g.state = ACTIVE;
|
|
return &if_handle_g;
|
|
}
|
|
#endif
|
|
|
|
esp_err_t ret = ESP_OK;
|
|
uint16_t prio_q_idx = 0;
|
|
|
|
/* Configuration for the SPI bus */
|
|
spi_bus_config_t buscfg={
|
|
.mosi_io_num=GPIO_MOSI,
|
|
.miso_io_num=GPIO_MISO,
|
|
.sclk_io_num=GPIO_SCLK,
|
|
.quadwp_io_num = -1,
|
|
.quadhd_io_num = -1,
|
|
.max_transfer_sz = SPI_BUFFER_SIZE,
|
|
#if 0
|
|
/*
|
|
* Moving ESP32 SPI slave interrupts in flash, Keeping it in IRAM gives crash,
|
|
* While performing flash erase operation.
|
|
*/
|
|
.intr_flags=ESP_INTR_FLAG_IRAM
|
|
#endif
|
|
};
|
|
|
|
/* Configuration for the SPI slave interface */
|
|
spi_slave_interface_config_t slvcfg={
|
|
.mode=ESP_SPI_MODE,
|
|
.spics_io_num=GPIO_CS,
|
|
.queue_size=SPI_QUEUE_SIZE,
|
|
.flags=0,
|
|
.post_setup_cb=spi_post_setup_cb,
|
|
.post_trans_cb=spi_post_trans_cb
|
|
};
|
|
|
|
if (!hosted_constructs_init_done) {
|
|
/* Configuration for the handshake line */
|
|
gpio_config_t io_conf={
|
|
.intr_type=GPIO_INTR_DISABLE,
|
|
.mode=GPIO_MODE_OUTPUT,
|
|
.pin_bit_mask=GPIO_MASK_HANDSHAKE
|
|
};
|
|
|
|
/* Configuration for data_ready line */
|
|
gpio_config_t io_data_ready_conf={
|
|
.intr_type=GPIO_INTR_DISABLE,
|
|
.mode=GPIO_MODE_OUTPUT,
|
|
.pin_bit_mask=GPIO_MASK_DATA_READY
|
|
};
|
|
|
|
spi_mempool_create();
|
|
|
|
/* Configure handshake and data_ready lines as output */
|
|
gpio_config(&io_conf);
|
|
gpio_config(&io_data_ready_conf);
|
|
reset_handshake_gpio();
|
|
reset_dataready_gpio();
|
|
|
|
/* Enable pull-ups on SPI lines
|
|
* so that no rogue pulses when no master is connected
|
|
*/
|
|
gpio_set_pull_mode(CONFIG_ESP_SPI_GPIO_HANDSHAKE, H_HS_PULL_REGISTER);
|
|
gpio_set_pull_mode(CONFIG_ESP_SPI_GPIO_DATA_READY, H_DR_PULL_REGISTER);
|
|
gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLUP_ONLY);
|
|
gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY);
|
|
gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY);
|
|
|
|
ESP_LOGI(TAG, "SPI Ctrl:%u mode: %u, Freq:ConfigAtHost\nGPIOs: CLK:%u MOSI:%u MISO:%u CS:%u HS:%u DR:%u\n",
|
|
ESP_SPI_CONTROLLER, slvcfg.mode,
|
|
GPIO_SCLK, GPIO_MOSI, GPIO_MISO, GPIO_CS,
|
|
GPIO_HANDSHAKE, GPIO_DATA_READY);
|
|
|
|
ESP_LOGI(TAG, "Hosted SPI queue size: Tx:%u Rx:%u", SPI_TX_QUEUE_SIZE, SPI_RX_QUEUE_SIZE);
|
|
register_hs_disable_pin(GPIO_CS);
|
|
|
|
#if !H_HANDSHAKE_ACTIVE_HIGH
|
|
ESP_LOGI(TAG, "Handshake: Active Low");
|
|
#endif
|
|
|
|
#if !H_DATAREADY_ACTIVE_HIGH
|
|
ESP_LOGI(TAG, "DataReady: Active Low");
|
|
#endif
|
|
}
|
|
|
|
|
|
/* Initialize SPI slave interface */
|
|
ret=spi_slave_initialize(ESP_SPI_CONTROLLER, &buscfg, &slvcfg, DMA_CHAN);
|
|
assert(ret==ESP_OK);
|
|
|
|
if (!hosted_constructs_init_done) {
|
|
//gpio_set_drive_capability(CONFIG_ESP_SPI_GPIO_HANDSHAKE, GPIO_DRIVE_CAP_3);
|
|
//gpio_set_drive_capability(CONFIG_ESP_SPI_GPIO_DATA_READY, GPIO_DRIVE_CAP_3);
|
|
gpio_set_drive_capability(GPIO_SCLK, GPIO_DRIVE_CAP_3);
|
|
gpio_set_drive_capability(GPIO_MISO, GPIO_DRIVE_CAP_3);
|
|
gpio_set_pull_mode(GPIO_MISO, GPIO_PULLDOWN_ONLY);
|
|
|
|
#if HS_DEASSERT_ON_CS
|
|
wait_cs_deassert_sem = xSemaphoreCreateBinary();
|
|
assert(wait_cs_deassert_sem!= NULL);
|
|
ret = xSemaphoreTake(wait_cs_deassert_sem, 0);
|
|
#endif
|
|
}
|
|
|
|
memset(&if_handle_g, 0, sizeof(if_handle_g));
|
|
if_handle_g.state = ACTIVE;
|
|
|
|
if (!hosted_constructs_init_done) {
|
|
spi_tx_sem = xSemaphoreCreateCounting(SPI_TX_QUEUE_SIZE*3, 0);
|
|
assert(spi_tx_sem != NULL);
|
|
spi_rx_sem = xSemaphoreCreateCounting(SPI_RX_QUEUE_SIZE*3, 0);
|
|
assert(spi_rx_sem != NULL);
|
|
|
|
for (prio_q_idx=0; prio_q_idx<MAX_PRIORITY_QUEUES;prio_q_idx++) {
|
|
spi_rx_queue[prio_q_idx] = xQueueCreate(SPI_RX_QUEUE_SIZE, sizeof(interface_buffer_handle_t));
|
|
assert(spi_rx_queue[prio_q_idx] != NULL);
|
|
|
|
spi_tx_queue[prio_q_idx] = xQueueCreate(SPI_TX_QUEUE_SIZE, sizeof(interface_buffer_handle_t));
|
|
assert(spi_tx_queue[prio_q_idx] != NULL);
|
|
}
|
|
|
|
assert(xTaskCreate(spi_transaction_post_process_task , "spi_post_process_task" ,
|
|
CONFIG_ESP_HOSTED_DEFAULT_TASK_STACK_SIZE, NULL,
|
|
CONFIG_ESP_HOSTED_DEFAULT_TASK_PRIORITY, NULL) == pdTRUE);
|
|
hosted_constructs_init_done = 1;
|
|
}
|
|
|
|
|
|
return &if_handle_g;
|
|
}
|
|
|
|
static int32_t esp_spi_write(interface_handle_t *handle, interface_buffer_handle_t *buf_handle)
|
|
{
|
|
int32_t total_len = 0;
|
|
uint16_t offset = 0;
|
|
struct esp_payload_header *header = NULL;
|
|
interface_buffer_handle_t tx_buf_handle = {0};
|
|
|
|
if (unlikely(handle->state < ACTIVE)) {
|
|
ESP_LOGE(TAG, "SPI is not active\n");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
if (unlikely(!handle || !buf_handle)) {
|
|
ESP_LOGE(TAG , "Invalid arguments\n");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
if (unlikely(!buf_handle->payload_len || !buf_handle->payload)) {
|
|
ESP_LOGE(TAG , "Invalid arguments, len:%d\n", buf_handle->payload_len);
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
total_len = buf_handle->payload_len + sizeof (struct esp_payload_header);
|
|
|
|
/* make the adresses dma aligned */
|
|
if (!IS_SPI_DMA_ALIGNED(total_len)) {
|
|
MAKE_SPI_DMA_ALIGNED(total_len);
|
|
}
|
|
|
|
if (unlikely(total_len > SPI_BUFFER_SIZE)) {
|
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
ESP_LOGE(TAG, "Max frame length exceeded %ld.. drop it\n", total_len);
|
|
#else
|
|
ESP_LOGE(TAG, "Max frame length exceeded %d.. drop it\n", total_len);
|
|
#endif
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
tx_buf_handle.if_type = buf_handle->if_type;
|
|
tx_buf_handle.if_num = buf_handle->if_num;
|
|
tx_buf_handle.payload_len = total_len;
|
|
|
|
tx_buf_handle.payload = spi_buffer_tx_alloc(MEMSET_REQUIRED);
|
|
assert(tx_buf_handle.payload);
|
|
|
|
header = (struct esp_payload_header *) tx_buf_handle.payload;
|
|
|
|
memset (header, 0, sizeof(struct esp_payload_header));
|
|
|
|
/* Initialize header */
|
|
header->if_type = buf_handle->if_type;
|
|
header->if_num = buf_handle->if_num;
|
|
header->len = htole16(buf_handle->payload_len);
|
|
offset = sizeof(struct esp_payload_header);
|
|
header->offset = htole16(offset);
|
|
header->seq_num = htole16(buf_handle->seq_num);
|
|
header->flags = buf_handle->flag;
|
|
|
|
tx_buf_handle.wifi_flow_ctrl_en = find_wifi_tx_throttling_to_be_set();
|
|
|
|
/* copy the data from caller */
|
|
memcpy(tx_buf_handle.payload + offset, buf_handle->payload, buf_handle->payload_len);
|
|
|
|
|
|
#if CONFIG_ESP_SPI_CHECKSUM
|
|
header->checksum = htole16(compute_checksum(tx_buf_handle.payload,
|
|
offset+buf_handle->payload_len));
|
|
#endif
|
|
|
|
if (header->if_type == ESP_SERIAL_IF)
|
|
xQueueSend(spi_tx_queue[PRIO_Q_SERIAL], &tx_buf_handle, portMAX_DELAY);
|
|
else if (header->if_type == ESP_HCI_IF)
|
|
xQueueSend(spi_tx_queue[PRIO_Q_BT], &tx_buf_handle, portMAX_DELAY);
|
|
else
|
|
xQueueSend(spi_tx_queue[PRIO_Q_OTHERS], &tx_buf_handle, portMAX_DELAY);
|
|
|
|
/* indicate waiting data on ready pin */
|
|
set_dataready_gpio();
|
|
|
|
xSemaphoreGive(spi_tx_sem);
|
|
|
|
return buf_handle->payload_len;
|
|
}
|
|
|
|
static void IRAM_ATTR esp_spi_read_done(void *handle)
|
|
{
|
|
spi_buffer_rx_free(handle);
|
|
}
|
|
|
|
static int esp_spi_read(interface_handle_t *if_handle, interface_buffer_handle_t *buf_handle)
|
|
{
|
|
if (unlikely(!if_handle)) {
|
|
ESP_LOGE(TAG, "Invalid arguments to esp_spi_read\n");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
if (likely(spi_rx_sem)) {
|
|
xSemaphoreTake(spi_rx_sem, portMAX_DELAY);
|
|
}
|
|
if (unlikely(if_handle->state < DEACTIVE)) {
|
|
ESP_LOGE(TAG, "spi slave bus inactive\n");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
if (pdFALSE == xQueueReceive(spi_rx_queue[PRIO_Q_SERIAL], buf_handle, 0))
|
|
if (pdFALSE == xQueueReceive(spi_rx_queue[PRIO_Q_BT], buf_handle, 0))
|
|
if (pdFALSE == xQueueReceive(spi_rx_queue[PRIO_Q_OTHERS], buf_handle, 0)) {
|
|
ESP_LOGI(TAG, "%s No element in rx queue", __func__);
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
return buf_handle->payload_len;
|
|
}
|
|
|
|
static esp_err_t esp_spi_reset(interface_handle_t *handle)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
ret = spi_slave_free(ESP_SPI_CONTROLLER);
|
|
if (ESP_OK != ret) {
|
|
ESP_LOGE(TAG, "spi slave bus free failed\n");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void esp_spi_deinit(interface_handle_t *handle)
|
|
{
|
|
if (!handle) {
|
|
return;
|
|
}
|
|
#if H_PS_UNLOAD_BUS_WHILE_PS
|
|
if (if_handle_g.state == DEINIT) {
|
|
ESP_LOGW(TAG, "SPI already deinitialized");
|
|
return;
|
|
}
|
|
#if H_IF_AVAILABLE_SPI_SLAVE_ENABLE_DISABLE
|
|
spi_slave_disable(ESP_SPI_CONTROLLER);
|
|
#endif
|
|
handle->state = DEINIT;
|
|
ESP_LOGI(TAG, "SPI deinit requested. Signaling spi task to exit.");
|
|
#endif
|
|
}
|