2018-06-08 03:32:43 -04:00
// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD
2017-01-06 01:20:32 -05:00
//
// 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.
/*
Architecture :
We can initialize a SPI driver , but we don ' t talk to the SPI driver itself , we address a device . A device essentially
is a combination of SPI port and CS pin , plus some information about the specifics of communication to the device
2018-01-30 22:15:23 -05:00
( timing , command / address length etc ) . The arbitration between tasks is also in conception of devices .
A device can work in interrupt mode and polling mode , and a third but
complicated mode which combines the two modes above :
1. Work in the ISR with a set of queues ; one per device .
The idea is that to send something to a SPI device , you allocate a
transaction descriptor . It contains some information about the transfer
like the lenghth , address , command etc , plus pointers to transmit and
receive buffer . The address of this block gets pushed into the transmit
queue . The SPI driver does its magic , and sends and retrieves the data
eventually . The data gets written to the receive buffers , if needed the
transaction descriptor is modified to indicate returned parameters and
the entire thing goes into the return queue , where whatever software
initiated the transaction can retrieve it .
The entire thing is run from the SPI interrupt handler . If SPI is done
transmitting / receiving but nothing is in the queue , it will not clear the
SPI interrupt but just disable it by esp_intr_disable . This way , when a
new thing is sent , pushing the packet into the send queue and re - enabling
the interrupt ( by esp_intr_enable ) will trigger the interrupt again , which
can then take care of the sending .
2. Work in the polling mode in the task .
In this mode we get rid of the ISR , FreeRTOS queue and task switching , the
task is no longer blocked during a transaction . This increase the cpu
load , but decrease the interval of SPI transactions . Each time only one
device ( in one task ) can send polling transactions , transactions to
other devices are blocked until the polling transaction of current device
is done .
In the polling mode , the queue is not used , all the operations are done
in the task . The task calls ` ` spi_device_polling_start ` ` to setup and start
a new transaction , then call ` ` spi_device_polling_end ` ` to handle the
return value of the transaction .
To handle the arbitration among devices , the device " temporarily " acquire
a bus by the ` ` device_acquire_bus_internal ` ` function , which writes
acquire_cs by CAS operation . Other devices which wants to send polling
transactions but don ' t own the bus will block and wait until given the
semaphore which indicates the ownership of bus .
In case of the ISR is still sending transactions to other devices , the ISR
should maintain an ` ` isr_free ` ` flag indicating that it ' s not doing
transactions . When the bus is acquired , the ISR can only send new
transactions to the acquiring device . The ISR will automatically disable
itself and send semaphore to the device if the ISR is free . If the device
sees the isr_free flag , it can directly start its polling transaction .
Otherwise it should block and wait for the semaphore from the ISR .
After the polling transaction , the driver will release the bus . During the
release of the bus , the driver search all other devices to see whether
there is any device waiting to acquire the bus , if so , acquire for it and
send it a semaphore if the device queue is empty , or invoke the ISR for
it . If all other devices don ' t need to acquire the bus , but there are
still transactions in the queues , the ISR will also be invoked .
To get better polling efficiency , user can call ` ` spi_device_acquire_bus ` `
function , which also calls the ` ` device_acquire_bus_internal ` ` function ,
before a series of polling transactions to a device . The bus acquiring and
task switching before and after the polling transaction will be escaped .
3. Mixed mode
The driver is written under the assumption that polling and interrupt
transactions are not happening simultaneously . When sending polling
transactions , it will check whether the ISR is active , which includes the
case the ISR is sending the interrupt transactions of the acquiring
device . If the ISR is still working , the routine sending a polling
transaction will get blocked and wait until the semaphore from the ISR
which indicates the ISR is free now .
A fatal case is , a polling transaction is in flight , but the ISR received
an interrupt transaction . The behavior of the driver is unpredictable ,
which should be strictly forbidden .
We have two bits to control the interrupt :
1. The slave - > trans_done bit , which is automatically asserted when a transaction is done .
This bit is cleared during an interrupt transaction , so that the interrupt
will be triggered when the transaction is done , or the SW can check the
bit to see if the transaction is done for polling transactions .
When no transaction is in - flight , the bit is kept active , so that the SW
can easily invoke the ISR by enable the interrupt .
2. The system interrupt enable / disable , controlled by esp_intr_enable and esp_intr_disable .
The interrupt is disabled ( by the ISR itself ) when no interrupt transaction
is queued . When the bus is not occupied , any task , which queues a
transaction into the queue , will enable the interrupt to invoke the ISR .
When the bus is occupied by a device , other device will put off the
invoking of ISR to the moment when the bus is released . The device
acquiring the bus can still send interrupt transactions by enable the
interrupt .
2017-01-06 01:20:32 -05:00
2018-01-30 22:15:23 -05:00
*/
2017-01-06 01:20:32 -05:00
# include <string.h>
2017-03-31 03:05:25 -04:00
# include "driver/spi_common.h"
2017-01-06 01:20:32 -05:00
# include "driver/spi_master.h"
# include "soc/dport_reg.h"
2018-06-08 03:32:43 -04:00
# include "soc/spi_periph.h"
2017-01-06 01:20:32 -05:00
# include "rom/ets_sys.h"
# include "esp_types.h"
# include "esp_attr.h"
# include "esp_intr.h"
# include "esp_intr_alloc.h"
# include "esp_log.h"
# include "esp_err.h"
2017-09-24 02:59:15 -04:00
# include "esp_pm.h"
2017-01-06 01:20:32 -05:00
# include "freertos/FreeRTOS.h"
# include "freertos/semphr.h"
# include "freertos/xtensa_api.h"
# include "freertos/task.h"
# include "soc/soc.h"
2017-05-05 00:16:02 -04:00
# include "soc/soc_memory_layout.h"
2017-01-06 01:20:32 -05:00
# include "soc/dport_reg.h"
# include "rom/lldesc.h"
# include "driver/gpio.h"
# include "driver/periph_ctrl.h"
2017-05-03 04:03:28 -04:00
# include "esp_heap_caps.h"
2018-01-30 22:15:23 -05:00
# include "stdatomic.h"
2017-01-06 01:20:32 -05:00
typedef struct spi_device_t spi_device_t ;
2017-12-14 03:08:13 -05:00
typedef typeof ( SPI1 . clock ) spi_clock_reg_t ;
2017-01-06 01:20:32 -05:00
# define NO_CS 3 //Number of CS pins per SPI host
2018-06-11 07:54:18 -04:00
# ifdef CONFIG_SPI_MASTER_ISR_IN_IRAM
# define SPI_MASTER_ISR_ATTR IRAM_ATTR
# else
# define SPI_MASTER_ISR_ATTR
# endif
# ifdef CONFIG_SPI_MASTER_IN_IRAM
# define SPI_MASTER_ATTR IRAM_ATTR
# else
# define SPI_MASTER_ATTR
# endif
2017-07-16 23:37:32 -04:00
/// struct to hold private transaction data (like tx and rx buffer for DMA).
2017-09-28 08:19:18 -04:00
typedef struct {
spi_transaction_t * trans ;
2018-01-30 22:15:23 -05:00
const uint32_t * buffer_to_send ; //equals to tx_data, if SPI_TRANS_USE_RXDATA is applied; otherwise if original buffer wasn't in DMA-capable memory, this gets the address of a temporary buffer that is;
2017-07-16 23:37:32 -04:00
//otherwise sets to the original buffer or NULL if no buffer is assigned.
uint32_t * buffer_to_rcv ; // similar to buffer_to_send
2018-01-30 22:15:23 -05:00
} spi_trans_priv_t ;
2017-07-16 23:37:32 -04:00
2017-01-06 01:20:32 -05:00
typedef struct {
2018-01-30 22:15:23 -05:00
_Atomic ( spi_device_t * ) device [ NO_CS ] ;
2017-01-06 01:20:32 -05:00
intr_handle_t intr ;
spi_dev_t * hw ;
2018-01-30 22:15:23 -05:00
spi_trans_priv_t cur_trans_buf ;
int cur_cs ; //current device doing transaction
int prev_cs ; //last device doing transaction, used to avoid re-configure registers if the device not changed
atomic_int acquire_cs ; //the device acquiring the bus, NO_CS if no one is doing so.
bool polling ; //in process of a polling, avoid of queue new transactions into ISR
bool isr_free ; //the isr is not sending transactions
bool bus_locked ; //the bus is controlled by a device
2017-03-31 03:05:25 -04:00
lldesc_t * dmadesc_tx ;
lldesc_t * dmadesc_rx ;
2018-03-21 08:42:45 -04:00
uint32_t flags ;
2017-03-31 03:05:25 -04:00
int dma_chan ;
int max_transfer_sz ;
2018-06-08 03:33:04 -04:00
spi_bus_config_t bus_cfg ;
2017-09-24 02:59:15 -04:00
# ifdef CONFIG_PM_ENABLE
esp_pm_lock_handle_t pm_lock ;
# endif
2017-01-06 01:20:32 -05:00
} spi_host_t ;
2017-12-14 03:08:13 -05:00
typedef struct {
spi_clock_reg_t reg ;
int eff_clk ;
2018-03-19 23:33:42 -04:00
int dummy_num ;
2017-09-28 08:19:18 -04:00
int miso_delay ;
2017-12-14 03:08:13 -05:00
} clock_config_t ;
2017-01-06 01:20:32 -05:00
struct spi_device_t {
2018-01-30 22:15:23 -05:00
int id ;
2017-01-06 01:20:32 -05:00
QueueHandle_t trans_queue ;
QueueHandle_t ret_queue ;
spi_device_interface_config_t cfg ;
2017-12-14 03:08:13 -05:00
clock_config_t clk_cfg ;
2017-01-06 01:20:32 -05:00
spi_host_t * host ;
2018-01-30 22:15:23 -05:00
SemaphoreHandle_t semphr_polling ; //semaphore to notify the device it claimed the bus
bool waiting ; //the device is waiting for the exclusive control of the bus
2017-01-06 01:20:32 -05:00
} ;
static spi_host_t * spihost [ 3 ] ;
static const char * SPI_TAG = " spi_master " ;
2017-09-28 08:19:18 -04:00
# define SPI_CHECK(a, str, ret_val, ...) \
2017-01-06 01:20:32 -05:00
if ( ! ( a ) ) { \
2017-09-28 08:19:18 -04:00
ESP_LOGE ( SPI_TAG , " %s(%d): " str , __FUNCTION__ , __LINE__ , # # __VA_ARGS__ ) ; \
2017-01-06 01:20:32 -05:00
return ( ret_val ) ; \
}
static void spi_intr ( void * arg ) ;
2017-04-13 13:33:33 -04:00
esp_err_t spi_bus_initialize ( spi_host_device_t host , const spi_bus_config_t * bus_config , int dma_chan )
2017-01-06 01:20:32 -05:00
{
2018-03-21 08:42:45 -04:00
bool spi_chan_claimed , dma_chan_claimed ;
2018-03-21 08:42:45 -04:00
esp_err_t ret = ESP_OK ;
esp_err_t err ;
2017-01-06 01:20:32 -05:00
/* ToDo: remove this when we have flash operations cooperating with this */
SPI_CHECK ( host ! = SPI_HOST , " SPI1 is not supported " , ESP_ERR_NOT_SUPPORTED ) ;
SPI_CHECK ( host > = SPI_HOST & & host < = VSPI_HOST , " invalid host " , ESP_ERR_INVALID_ARG ) ;
2017-08-31 07:59:30 -04:00
SPI_CHECK ( dma_chan > = 0 & & dma_chan < = 2 , " invalid dma channel " , ESP_ERR_INVALID_ARG ) ;
2017-03-31 03:05:25 -04:00
2017-08-31 07:59:30 -04:00
spi_chan_claimed = spicommon_periph_claim ( host ) ;
SPI_CHECK ( spi_chan_claimed , " host already in use " , ESP_ERR_INVALID_STATE ) ;
if ( dma_chan ! = 0 ) {
dma_chan_claimed = spicommon_dma_chan_claim ( dma_chan ) ;
if ( ! dma_chan_claimed ) {
spicommon_periph_free ( host ) ;
2018-01-30 22:15:23 -05:00
SPI_CHECK ( false , " dma channel already in use " , ESP_ERR_INVALID_STATE ) ;
2017-08-31 07:59:30 -04:00
}
}
2017-03-31 03:05:25 -04:00
spihost [ host ] = malloc ( sizeof ( spi_host_t ) ) ;
2018-03-21 08:42:45 -04:00
if ( spihost [ host ] = = NULL ) {
ret = ESP_ERR_NO_MEM ;
goto cleanup ;
}
2017-01-06 01:20:32 -05:00
memset ( spihost [ host ] , 0 , sizeof ( spi_host_t ) ) ;
2018-06-08 03:33:04 -04:00
memcpy ( & spihost [ host ] - > bus_cfg , bus_config , sizeof ( spi_bus_config_t ) ) ;
2017-09-24 02:59:15 -04:00
# ifdef CONFIG_PM_ENABLE
2018-03-21 08:42:45 -04:00
err = esp_pm_lock_create ( ESP_PM_APB_FREQ_MAX , 0 , " spi_master " ,
2017-09-24 02:59:15 -04:00
& spihost [ host ] - > pm_lock ) ;
if ( err ! = ESP_OK ) {
2018-03-21 08:42:45 -04:00
ret = err ;
goto cleanup ;
2017-09-24 02:59:15 -04:00
}
# endif //CONFIG_PM_ENABLE
2018-03-21 08:42:45 -04:00
err = spicommon_bus_initialize_io ( host , bus_config , dma_chan , SPICOMMON_BUSFLAG_MASTER | bus_config - > flags , & spihost [ host ] - > flags ) ;
2018-03-21 08:42:45 -04:00
if ( err ! = ESP_OK ) {
ret = err ;
goto cleanup ;
}
2018-03-21 08:42:45 -04:00
2017-03-31 03:05:25 -04:00
spihost [ host ] - > dma_chan = dma_chan ;
if ( dma_chan = = 0 ) {
2018-08-16 03:44:06 -04:00
spihost [ host ] - > max_transfer_sz = 64 ;
2017-01-06 01:20:32 -05:00
} else {
2017-03-31 03:05:25 -04:00
//See how many dma descriptors we need and allocate them
int dma_desc_ct = ( bus_config - > max_transfer_sz + SPI_MAX_DMA_LEN - 1 ) / SPI_MAX_DMA_LEN ;
2018-01-30 22:15:23 -05:00
if ( dma_desc_ct = = 0 ) dma_desc_ct = 1 ; //default to 4k when max is not given
2017-03-31 03:05:25 -04:00
spihost [ host ] - > max_transfer_sz = dma_desc_ct * SPI_MAX_DMA_LEN ;
2017-05-03 04:03:28 -04:00
spihost [ host ] - > dmadesc_tx = heap_caps_malloc ( sizeof ( lldesc_t ) * dma_desc_ct , MALLOC_CAP_DMA ) ;
spihost [ host ] - > dmadesc_rx = heap_caps_malloc ( sizeof ( lldesc_t ) * dma_desc_ct , MALLOC_CAP_DMA ) ;
2018-03-21 08:42:45 -04:00
if ( ! spihost [ host ] - > dmadesc_tx | | ! spihost [ host ] - > dmadesc_rx ) {
ret = ESP_ERR_NO_MEM ;
goto cleanup ;
}
}
2017-09-28 08:19:18 -04:00
2018-06-11 07:54:18 -04:00
int flags = ESP_INTR_FLAG_INTRDISABLED ;
# ifdef CONFIG_SPI_MASTER_ISR_IN_IRAM
flags | = ESP_INTR_FLAG_IRAM ;
# endif
err = esp_intr_alloc ( spicommon_irqsource_for_host ( host ) , flags , spi_intr , ( void * ) spihost [ host ] , & spihost [ host ] - > intr ) ;
2018-03-21 08:42:45 -04:00
if ( err ! = ESP_OK ) {
ret = err ;
goto cleanup ;
2017-01-06 01:20:32 -05:00
}
2017-03-31 03:05:25 -04:00
spihost [ host ] - > hw = spicommon_hw_for_host ( host ) ;
2017-01-06 01:20:32 -05:00
2017-07-16 23:37:32 -04:00
spihost [ host ] - > cur_cs = NO_CS ;
2017-12-14 04:38:54 -05:00
spihost [ host ] - > prev_cs = NO_CS ;
2018-01-30 22:15:23 -05:00
atomic_store ( & spihost [ host ] - > acquire_cs , NO_CS ) ;
spihost [ host ] - > polling = false ;
spihost [ host ] - > isr_free = true ;
spihost [ host ] - > bus_locked = false ;
2017-07-16 23:37:32 -04:00
2017-01-06 01:20:32 -05:00
//Reset DMA
2017-03-31 03:05:25 -04:00
spihost [ host ] - > hw - > dma_conf . val | = SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST ;
2017-01-06 01:20:32 -05:00
spihost [ host ] - > hw - > dma_out_link . start = 0 ;
spihost [ host ] - > hw - > dma_in_link . start = 0 ;
2017-03-31 03:05:25 -04:00
spihost [ host ] - > hw - > dma_conf . val & = ~ ( SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST ) ;
2017-03-02 05:46:59 -05:00
//Reset timing
spihost [ host ] - > hw - > ctrl2 . val = 0 ;
2018-08-16 03:44:06 -04:00
//master use all 64 bytes of the buffer
spihost [ host ] - > hw - > user . usr_miso_highpart = 0 ;
spihost [ host ] - > hw - > user . usr_mosi_highpart = 0 ;
2017-01-06 01:20:32 -05:00
//Disable unneeded ints
spihost [ host ] - > hw - > slave . rd_buf_done = 0 ;
spihost [ host ] - > hw - > slave . wr_buf_done = 0 ;
spihost [ host ] - > hw - > slave . rd_sta_done = 0 ;
spihost [ host ] - > hw - > slave . wr_sta_done = 0 ;
spihost [ host ] - > hw - > slave . rd_buf_inten = 0 ;
spihost [ host ] - > hw - > slave . wr_buf_inten = 0 ;
spihost [ host ] - > hw - > slave . rd_sta_inten = 0 ;
spihost [ host ] - > hw - > slave . wr_sta_inten = 0 ;
//Force a transaction done interrupt. This interrupt won't fire yet because we initialized the SPI interrupt as
2017-09-28 08:19:18 -04:00
//disabled. This way, we can just enable the SPI interrupt and the interrupt handler will kick in, handling
2017-01-06 01:20:32 -05:00
//any transactions that are queued.
spihost [ host ] - > hw - > slave . trans_inten = 1 ;
spihost [ host ] - > hw - > slave . trans_done = 1 ;
return ESP_OK ;
2017-03-31 03:05:25 -04:00
2018-03-21 08:42:45 -04:00
cleanup :
2017-03-31 03:05:25 -04:00
if ( spihost [ host ] ) {
free ( spihost [ host ] - > dmadesc_tx ) ;
free ( spihost [ host ] - > dmadesc_rx ) ;
2017-09-24 02:59:15 -04:00
# ifdef CONFIG_PM_ENABLE
if ( spihost [ host ] - > pm_lock ) {
esp_pm_lock_delete ( spihost [ host ] - > pm_lock ) ;
}
# endif
2017-03-31 03:05:25 -04:00
}
free ( spihost [ host ] ) ;
2018-01-30 22:15:23 -05:00
spihost [ host ] = NULL ;
2017-03-31 03:05:25 -04:00
spicommon_periph_free ( host ) ;
2017-09-22 05:47:06 -04:00
spicommon_dma_chan_free ( dma_chan ) ;
2018-03-21 08:42:45 -04:00
return ret ;
2017-01-06 01:20:32 -05:00
}
esp_err_t spi_bus_free ( spi_host_device_t host )
{
int x ;
SPI_CHECK ( host > = SPI_HOST & & host < = VSPI_HOST , " invalid host " , ESP_ERR_INVALID_ARG ) ;
SPI_CHECK ( spihost [ host ] ! = NULL , " host not in use " , ESP_ERR_INVALID_STATE ) ;
for ( x = 0 ; x < NO_CS ; x + + ) {
2018-01-30 22:15:23 -05:00
SPI_CHECK ( atomic_load ( & spihost [ host ] - > device [ x ] ) = = NULL , " not all CSses freed " , ESP_ERR_INVALID_STATE ) ;
2017-01-06 01:20:32 -05:00
}
2018-06-08 03:33:04 -04:00
spicommon_bus_free_io_cfg ( & spihost [ host ] - > bus_cfg ) ;
2017-08-31 07:59:30 -04:00
if ( spihost [ host ] - > dma_chan > 0 ) {
spicommon_dma_chan_free ( spihost [ host ] - > dma_chan ) ;
}
2017-09-24 02:59:15 -04:00
# ifdef CONFIG_PM_ENABLE
esp_pm_lock_delete ( spihost [ host ] - > pm_lock ) ;
# endif
2017-01-06 01:20:32 -05:00
spihost [ host ] - > hw - > slave . trans_inten = 0 ;
spihost [ host ] - > hw - > slave . trans_done = 0 ;
esp_intr_free ( spihost [ host ] - > intr ) ;
2017-03-31 03:05:25 -04:00
spicommon_periph_free ( host ) ;
2017-05-02 16:42:45 -04:00
free ( spihost [ host ] - > dmadesc_tx ) ;
free ( spihost [ host ] - > dmadesc_rx ) ;
2017-01-06 01:20:32 -05:00
free ( spihost [ host ] ) ;
spihost [ host ] = NULL ;
return ESP_OK ;
}
2017-09-28 08:19:18 -04:00
void spi_get_timing ( bool gpio_is_used , int input_delay_ns , int eff_clk , int * dummy_o , int * cycles_remain_o )
2018-03-19 23:33:42 -04:00
{
2017-09-28 08:19:18 -04:00
const int apbclk_kHz = APB_CLK_FREQ / 1000 ;
const int apbclk_n = APB_CLK_FREQ / eff_clk ;
2018-01-30 22:15:23 -05:00
const int gpio_delay_ns = gpio_is_used ? 25 : 0 ;
2017-09-28 08:19:18 -04:00
//calculate how many apb clocks a period has, the 1 is to compensate in case ``input_delay_ns`` is rounded off.
int apb_period_n = ( 1 + input_delay_ns + gpio_delay_ns ) * apbclk_kHz / 1000 / 1000 ;
int dummy_required = apb_period_n / apbclk_n ;
int miso_delay = 0 ;
if ( dummy_required > 0 ) {
//due to the clock delay between master and slave, there's a range in which data is random
//give MISO a delay if needed to make sure we sample at the time MISO is stable
miso_delay = ( dummy_required + 1 ) * apbclk_n - apb_period_n - 1 ;
2018-03-19 23:33:42 -04:00
} else {
2017-09-28 08:19:18 -04:00
//if the dummy is not required, maybe we should also delay half a SPI clock if the data comes too early
if ( apb_period_n * 4 < = apbclk_n ) miso_delay = - 1 ;
2018-03-19 23:33:42 -04:00
}
2017-09-28 08:19:18 -04:00
if ( dummy_o ! = NULL ) * dummy_o = dummy_required ;
if ( cycles_remain_o ! = NULL ) * cycles_remain_o = miso_delay ;
ESP_LOGD ( SPI_TAG , " eff: %d, limit: %dk(/%d), %d dummy, %d delay " , eff_clk / 1000 , apbclk_kHz / ( apb_period_n + 1 ) , apb_period_n , dummy_required , miso_delay ) ;
}
int spi_get_freq_limit ( bool gpio_is_used , int input_delay_ns )
{
const int apbclk_kHz = APB_CLK_FREQ / 1000 ;
2018-01-30 22:15:23 -05:00
const int gpio_delay_ns = gpio_is_used ? 25 : 0 ;
2017-09-28 08:19:18 -04:00
//calculate how many apb clocks a period has, the 1 is to compensate in case ``input_delay_ns`` is rounded off.
int apb_period_n = ( 1 + input_delay_ns + gpio_delay_ns ) * apbclk_kHz / 1000 / 1000 ;
return APB_CLK_FREQ / ( apb_period_n + 1 ) ;
2018-03-19 23:33:42 -04:00
}
2017-01-06 01:20:32 -05:00
/*
Add a device . This allocates a CS line for the device , allocates memory for the device structure and hooks
up the CS pin to whatever is specified .
*/
2018-03-07 13:28:41 -05:00
esp_err_t spi_bus_add_device ( spi_host_device_t host , const spi_device_interface_config_t * dev_config , spi_device_handle_t * handle )
2017-01-06 01:20:32 -05:00
{
int freecs ;
2017-03-02 05:46:59 -05:00
int apbclk = APB_CLK_FREQ ;
2018-03-19 23:33:42 -04:00
int eff_clk ;
int duty_cycle ;
2017-09-28 08:19:18 -04:00
int dummy_required ;
int miso_delay ;
2018-03-19 23:33:42 -04:00
spi_clock_reg_t clk_reg ;
2017-01-06 01:20:32 -05:00
SPI_CHECK ( host > = SPI_HOST & & host < = VSPI_HOST , " invalid host " , ESP_ERR_INVALID_ARG ) ;
SPI_CHECK ( spihost [ host ] ! = NULL , " host not initialized " , ESP_ERR_INVALID_STATE ) ;
SPI_CHECK ( dev_config - > spics_io_num < 0 | | GPIO_IS_VALID_OUTPUT_GPIO ( dev_config - > spics_io_num ) , " spics pin invalid " , ESP_ERR_INVALID_ARG ) ;
2017-01-11 03:13:33 -05:00
SPI_CHECK ( dev_config - > clock_speed_hz > 0 , " invalid sclk speed " , ESP_ERR_INVALID_ARG ) ;
2017-01-06 01:20:32 -05:00
for ( freecs = 0 ; freecs < NO_CS ; freecs + + ) {
//See if this slot is free; reserve if it is by putting a dummy pointer in the slot. We use an atomic compare&swap to make this thread-safe.
2018-01-30 22:15:23 -05:00
void * null = NULL ;
if ( atomic_compare_exchange_strong ( & spihost [ host ] - > device [ freecs ] , & null , ( spi_device_t * ) 1 ) ) break ;
2017-01-06 01:20:32 -05:00
}
SPI_CHECK ( freecs ! = NO_CS , " no free cs pins for host " , ESP_ERR_NOT_FOUND ) ;
//The hardware looks like it would support this, but actually setting cs_ena_pretrans when transferring in full
//duplex mode does absolutely nothing on the ESP32.
2018-06-19 08:38:17 -04:00
SPI_CHECK ( dev_config - > cs_ena_pretrans < = 1 | | ( dev_config - > address_bits = = 0 & & dev_config - > command_bits = = 0 ) | |
2017-09-28 08:19:18 -04:00
( dev_config - > flags & SPI_DEVICE_HALFDUPLEX ) , " In full-duplex mode, only support cs pretrans delay = 1 and without address_bits and command_bits " , ESP_ERR_INVALID_ARG ) ;
2018-01-30 22:15:23 -05:00
duty_cycle = ( dev_config - > duty_cycle_pos = = 0 ) ? 128 : dev_config - > duty_cycle_pos ;
2017-09-28 08:19:18 -04:00
eff_clk = spi_cal_clock ( apbclk , dev_config - > clock_speed_hz , duty_cycle , ( uint32_t * ) & clk_reg ) ;
int freq_limit = spi_get_freq_limit ( ! ( spihost [ host ] - > flags & SPICOMMON_BUSFLAG_NATIVE_PINS ) , dev_config - > input_delay_ns ) ;
//GPIO matrix can only change data at 80Mhz rate, which only allows 40MHz SPI clock.
2018-06-05 13:36:01 -04:00
SPI_CHECK ( eff_clk < = 40 * 1000 * 1000 | | spihost [ host ] - > flags & SPICOMMON_BUSFLAG_NATIVE_PINS , " 80MHz only supported on iomux pins " , ESP_ERR_INVALID_ARG ) ;
2017-09-28 08:19:18 -04:00
//Speed >=40MHz over GPIO matrix needs a dummy cycle, but these don't work for full-duplex connections.
spi_get_timing ( ! ( spihost [ host ] - > flags & SPICOMMON_BUSFLAG_NATIVE_PINS ) , dev_config - > input_delay_ns , eff_clk , & dummy_required , & miso_delay ) ;
SPI_CHECK ( dev_config - > flags & SPI_DEVICE_HALFDUPLEX | | dummy_required = = 0 | |
2018-03-19 23:33:42 -04:00
dev_config - > flags & SPI_DEVICE_NO_DUMMY ,
2017-09-28 08:19:18 -04:00
" When GPIO matrix is used in full-duplex mode at frequency > %.1fMHz, device cannot read correct data. \n \
2018-03-19 23:33:42 -04:00
Please note the SPI can only work at divisors of 80 MHz , and the driver always tries to find the closest frequency to your configuration . \ n \
2017-09-28 08:19:18 -04:00
Specify ` ` SPI_DEVICE_NO_DUMMY ` ` to ignore this checking . Then you can output data at higher speed , or read data at your own risk . " ,
ESP_ERR_INVALID_ARG , freq_limit / 1000. / 1000 ) ;
2017-01-06 01:20:32 -05:00
//Allocate memory for device
spi_device_t * dev = malloc ( sizeof ( spi_device_t ) ) ;
2017-03-31 03:05:25 -04:00
if ( dev = = NULL ) goto nomem ;
2017-01-06 01:20:32 -05:00
memset ( dev , 0 , sizeof ( spi_device_t ) ) ;
2018-01-30 22:15:23 -05:00
atomic_store ( & spihost [ host ] - > device [ freecs ] , dev ) ;
dev - > id = freecs ;
dev - > waiting = false ;
2017-01-06 01:20:32 -05:00
//Allocate queues, set defaults
2018-01-30 22:15:23 -05:00
dev - > trans_queue = xQueueCreate ( dev_config - > queue_size , sizeof ( spi_trans_priv_t ) ) ;
dev - > ret_queue = xQueueCreate ( dev_config - > queue_size , sizeof ( spi_trans_priv_t ) ) ;
dev - > semphr_polling = xSemaphoreCreateBinary ( ) ;
if ( ! dev - > trans_queue | | ! dev - > ret_queue | | ! dev - > semphr_polling ) {
goto nomem ;
}
2017-01-06 01:20:32 -05:00
dev - > host = spihost [ host ] ;
//We want to save a copy of the dev config in the dev struct.
memcpy ( & dev - > cfg , dev_config , sizeof ( spi_device_interface_config_t ) ) ;
2018-03-19 23:33:42 -04:00
dev - > cfg . duty_cycle_pos = duty_cycle ;
2017-09-28 08:19:18 -04:00
// TODO: if we have to change the apb clock among transactions, re-calculate this each time the apb clock lock is acquired.
2018-03-19 23:33:42 -04:00
dev - > clk_cfg = ( clock_config_t ) {
. eff_clk = eff_clk ,
2017-09-28 08:19:18 -04:00
. dummy_num = dummy_required ,
2018-03-19 23:33:42 -04:00
. reg = clk_reg ,
2017-09-28 08:19:18 -04:00
. miso_delay = miso_delay ,
2018-03-19 23:33:42 -04:00
} ;
2017-01-06 01:20:32 -05:00
//Set CS pin, CS options
2017-11-15 02:31:22 -05:00
if ( dev_config - > spics_io_num > = 0 ) {
2018-03-21 08:42:45 -04:00
spicommon_cs_initialize ( host , dev_config - > spics_io_num , freecs , ! ( spihost [ host ] - > flags & SPICOMMON_BUSFLAG_NATIVE_PINS ) ) ;
2017-01-06 01:20:32 -05:00
}
if ( dev_config - > flags & SPI_DEVICE_CLK_AS_CS ) {
spihost [ host ] - > hw - > pin . master_ck_sel | = ( 1 < < freecs ) ;
} else {
spihost [ host ] - > hw - > pin . master_ck_sel & = ( 1 < < freecs ) ;
}
if ( dev_config - > flags & SPI_DEVICE_POSITIVE_CS ) {
spihost [ host ] - > hw - > pin . master_cs_pol | = ( 1 < < freecs ) ;
} else {
spihost [ host ] - > hw - > pin . master_cs_pol & = ( 1 < < freecs ) ;
}
2017-09-28 08:19:18 -04:00
spihost [ host ] - > hw - > ctrl2 . mosi_delay_mode = 0 ;
spihost [ host ] - > hw - > ctrl2 . mosi_delay_num = 0 ;
2017-01-06 01:20:32 -05:00
* handle = dev ;
2017-12-14 03:08:13 -05:00
ESP_LOGD ( SPI_TAG , " SPI%d: New device added to CS%d, effective clock: %dkHz " , host , freecs , dev - > clk_cfg . eff_clk / 1000 ) ;
2017-01-06 01:20:32 -05:00
return ESP_OK ;
2017-03-31 03:05:25 -04:00
nomem :
if ( dev ) {
if ( dev - > trans_queue ) vQueueDelete ( dev - > trans_queue ) ;
if ( dev - > ret_queue ) vQueueDelete ( dev - > ret_queue ) ;
2018-01-30 22:15:23 -05:00
if ( dev - > semphr_polling ) vSemaphoreDelete ( dev - > semphr_polling ) ;
2017-03-31 03:05:25 -04:00
}
free ( dev ) ;
return ESP_ERR_NO_MEM ;
2017-01-06 01:20:32 -05:00
}
esp_err_t spi_bus_remove_device ( spi_device_handle_t handle )
{
int x ;
SPI_CHECK ( handle ! = NULL , " invalid handle " , ESP_ERR_INVALID_ARG ) ;
//These checks aren't exhaustive; another thread could sneak in a transaction inbetween. These are only here to
//catch design errors and aren't meant to be triggered during normal operation.
SPI_CHECK ( uxQueueMessagesWaiting ( handle - > trans_queue ) = = 0 , " Have unfinished transactions " , ESP_ERR_INVALID_STATE ) ;
2018-01-30 22:15:23 -05:00
SPI_CHECK ( handle - > host - > cur_cs = = NO_CS | | atomic_load ( & handle - > host - > device [ handle - > host - > cur_cs ] ) ! = handle , " Have unfinished transactions " , ESP_ERR_INVALID_STATE ) ;
2017-01-06 01:20:32 -05:00
SPI_CHECK ( uxQueueMessagesWaiting ( handle - > ret_queue ) = = 0 , " Have unfinished transactions " , ESP_ERR_INVALID_STATE ) ;
2018-06-27 06:41:57 -04:00
//return
2018-06-08 03:33:04 -04:00
int spics_io_num = handle - > cfg . spics_io_num ;
if ( spics_io_num > = 0 ) spicommon_cs_free_io ( spics_io_num ) ;
2017-01-06 01:20:32 -05:00
//Kill queues
vQueueDelete ( handle - > trans_queue ) ;
vQueueDelete ( handle - > ret_queue ) ;
2018-01-30 22:15:23 -05:00
vSemaphoreDelete ( handle - > semphr_polling ) ;
2017-01-06 01:20:32 -05:00
//Remove device from list of csses and free memory
for ( x = 0 ; x < NO_CS ; x + + ) {
2018-01-30 22:15:23 -05:00
if ( atomic_load ( & handle - > host - > device [ x ] ) = = handle ) {
atomic_store ( & handle - > host - > device [ x ] , NULL ) ;
if ( x = = handle - > host - > prev_cs ) handle - > host - > prev_cs = NO_CS ;
2017-12-14 04:38:54 -05:00
}
2017-01-06 01:20:32 -05:00
}
free ( handle ) ;
return ESP_OK ;
}
2018-01-30 22:15:23 -05:00
static int spi_freq_for_pre_n ( int fapb , int pre , int n )
{
2017-01-11 00:01:48 -05:00
return ( fapb / ( pre * n ) ) ;
}
2017-12-14 03:08:13 -05:00
int spi_cal_clock ( int fapb , int hz , int duty_cycle , uint32_t * reg_o )
{
spi_clock_reg_t reg ;
int eff_clk ;
2017-01-11 00:01:48 -05:00
2017-01-11 01:13:37 -05:00
//In hw, n, h and l are 1-64, pre is 1-8K. Value written to register is one lower than used value.
2017-01-11 00:01:48 -05:00
if ( hz > ( ( fapb / 4 ) * 3 ) ) {
//Using Fapb directly will give us the best result here.
2017-12-14 03:08:13 -05:00
reg . clkcnt_l = 0 ;
reg . clkcnt_h = 0 ;
reg . clkcnt_n = 0 ;
reg . clkdiv_pre = 0 ;
reg . clk_equ_sysclk = 1 ;
2017-03-02 05:46:59 -05:00
eff_clk = fapb ;
2017-01-06 01:20:32 -05:00
} else {
2017-01-11 00:01:48 -05:00
//For best duty cycle resolution, we want n to be as close to 32 as possible, but
//we also need a pre/n combo that gets us as close as possible to the intended freq.
//To do this, we bruteforce n and calculate the best pre to go along with that.
//If there's a choice between pre/n combos that give the same result, use the one
//with the higher n.
2017-12-14 03:08:13 -05:00
int pre , n , h , l ;
2017-01-11 00:01:48 -05:00
int bestn = - 1 ;
int bestpre = - 1 ;
2017-01-11 03:13:33 -05:00
int besterr = 0 ;
2017-01-11 00:01:48 -05:00
int errval ;
2017-04-24 04:13:22 -04:00
for ( n = 2 ; n < = 64 ; n + + ) { //Start at 2: we need to be able to set h/l so we have at least one high and one low pulse.
2017-01-11 00:01:48 -05:00
//Effectively, this does pre=round((fapb/n)/hz).
pre = ( ( fapb / n ) + ( hz / 2 ) ) / hz ;
2017-01-11 03:13:33 -05:00
if ( pre < = 0 ) pre = 1 ;
2017-01-11 00:01:48 -05:00
if ( pre > 8192 ) pre = 8192 ;
errval = abs ( spi_freq_for_pre_n ( fapb , pre , n ) - hz ) ;
2017-01-11 03:13:33 -05:00
if ( bestn = = - 1 | | errval < = besterr ) {
2017-01-11 00:01:48 -05:00
besterr = errval ;
bestn = n ;
bestpre = pre ;
}
2017-01-06 01:20:32 -05:00
}
2017-01-11 00:01:48 -05:00
n = bestn ;
pre = bestpre ;
l = n ;
//This effectively does round((duty_cycle*n)/256)
h = ( duty_cycle * n + 127 ) / 256 ;
if ( h < = 0 ) h = 1 ;
2017-12-14 03:08:13 -05:00
reg . clk_equ_sysclk = 0 ;
reg . clkcnt_n = n - 1 ;
reg . clkdiv_pre = pre - 1 ;
reg . clkcnt_h = h - 1 ;
reg . clkcnt_l = l - 1 ;
2017-03-02 05:46:59 -05:00
eff_clk = spi_freq_for_pre_n ( fapb , pre , n ) ;
2017-01-06 01:20:32 -05:00
}
2018-01-30 22:15:23 -05:00
if ( reg_o ! = NULL ) * reg_o = reg . val ;
2017-03-02 05:46:59 -05:00
return eff_clk ;
2017-01-06 01:20:32 -05:00
}
2017-12-14 03:08:13 -05:00
/*
* Set the spi clock according to pre - calculated register value .
*/
2018-01-30 22:15:23 -05:00
static inline void SPI_MASTER_ISR_ATTR spi_set_clock ( spi_dev_t * hw , spi_clock_reg_t reg )
{
2017-12-14 03:08:13 -05:00
hw - > clock . val = reg . val ;
}
2017-01-06 01:20:32 -05:00
2018-01-30 22:15:23 -05:00
// Setup the device-specified configuration registers. Called every time a new
// transaction is to be sent, but only apply new configurations when the device
// changes.
static void SPI_MASTER_ISR_ATTR spi_setup_device ( spi_host_t * host , int dev_id )
2017-01-06 01:20:32 -05:00
{
2018-01-30 22:15:23 -05:00
//if the configuration is already applied, skip the following.
if ( dev_id = = host - > prev_cs ) {
return ;
}
2017-01-06 01:20:32 -05:00
2018-01-30 22:15:23 -05:00
ESP_EARLY_LOGD ( SPI_TAG , " SPI device changed from %d to %d " , host - > prev_cs , dev_id ) ;
spi_device_t * dev = atomic_load ( & host - > device [ dev_id ] ) ;
//Configure clock settings
spi_set_clock ( host - > hw , dev - > clk_cfg . reg ) ;
//Configure bit order
host - > hw - > ctrl . rd_bit_order = ( dev - > cfg . flags & SPI_DEVICE_RXBIT_LSBFIRST ) ? 1 : 0 ;
host - > hw - > ctrl . wr_bit_order = ( dev - > cfg . flags & SPI_DEVICE_TXBIT_LSBFIRST ) ? 1 : 0 ;
//Configure polarity
if ( dev - > cfg . mode = = 0 ) {
host - > hw - > pin . ck_idle_edge = 0 ;
host - > hw - > user . ck_out_edge = 0 ;
} else if ( dev - > cfg . mode = = 1 ) {
host - > hw - > pin . ck_idle_edge = 0 ;
host - > hw - > user . ck_out_edge = 1 ;
} else if ( dev - > cfg . mode = = 2 ) {
host - > hw - > pin . ck_idle_edge = 1 ;
host - > hw - > user . ck_out_edge = 1 ;
} else if ( dev - > cfg . mode = = 3 ) {
host - > hw - > pin . ck_idle_edge = 1 ;
host - > hw - > user . ck_out_edge = 0 ;
}
//Configure misc stuff
host - > hw - > user . doutdin = ( dev - > cfg . flags & SPI_DEVICE_HALFDUPLEX ) ? 0 : 1 ;
host - > hw - > user . sio = ( dev - > cfg . flags & SPI_DEVICE_3WIRE ) ? 1 : 0 ;
//Configure CS pin and timing
host - > hw - > ctrl2 . setup_time = dev - > cfg . cs_ena_pretrans - 1 ;
host - > hw - > user . cs_setup = dev - > cfg . cs_ena_pretrans ? 1 : 0 ;
//set hold_time to 0 will not actually append delay to CS
//set it to 1 since we do need at least one clock of hold time in most cases
host - > hw - > ctrl2 . hold_time = dev - > cfg . cs_ena_posttrans ;
if ( host - > hw - > ctrl2 . hold_time = = 0 ) host - > hw - > ctrl2 . hold_time = 1 ;
host - > hw - > user . cs_hold = 1 ;
host - > hw - > pin . cs0_dis = ( dev_id = = 0 ) ? 0 : 1 ;
host - > hw - > pin . cs1_dis = ( dev_id = = 1 ) ? 0 : 1 ;
host - > hw - > pin . cs2_dis = ( dev_id = = 2 ) ? 0 : 1 ;
//Record the device just configured to save time for next time
host - > prev_cs = dev_id ;
}
2017-01-06 01:20:32 -05:00
2018-01-30 22:15:23 -05:00
/*-----------------------------------------------------------------------------
Arbitration Functions
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static inline void spi_isr_invoke ( spi_device_t * dev )
{
int acquire_cs = atomic_load ( & dev - > host - > acquire_cs ) ;
if ( acquire_cs = = dev - > id | | acquire_cs = = NO_CS ) {
esp_intr_enable ( dev - > host - > intr ) ;
}
//otherwise wait for bus release to invoke
}
/* This function try to race for the arbitration between devices.
* Even if this returns successfully , the ISR may be still running .
* Call device_wait_for_isr_idle to make sure the ISR is done .
*/
static SPI_MASTER_ISR_ATTR esp_err_t device_acquire_bus_internal ( spi_device_t * handle , TickType_t wait )
{
spi_host_t * host = handle - > host ;
SPI_CHECK ( wait = = portMAX_DELAY , " acquire finite time not supported now. " , ESP_ERR_INVALID_ARG ) ;
if ( atomic_load ( & host - > acquire_cs ) = = handle - > id ) {
// Quickly skip if the bus is already acquired.
// Usually this is only when the bus is locked.
assert ( host - > bus_locked ) ;
return ESP_OK ;
} else {
// Declare we are waiting for the bus so that if we get blocked later, other device or the ISR will yield to us after their using.
handle - > waiting = true ;
// Clear the semaphore before checking
xSemaphoreTake ( handle - > semphr_polling , 0 ) ;
int no_cs = NO_CS ;
atomic_compare_exchange_weak ( & host - > acquire_cs , & no_cs , handle - > id ) ;
if ( atomic_load ( & host - > acquire_cs ) ! = handle - > id ) {
//block until the bus is acquired (help by other task)
BaseType_t ret = xSemaphoreTake ( handle - > semphr_polling , wait ) ;
//TODO: add timeout handling here.
if ( ret = = pdFALSE ) return ESP_ERR_TIMEOUT ;
2017-01-06 01:20:32 -05:00
}
2018-01-30 22:15:23 -05:00
handle - > waiting = false ;
2017-01-06 01:20:32 -05:00
}
2018-01-30 22:15:23 -05:00
return ESP_OK ;
}
2017-07-16 23:37:32 -04:00
2018-01-30 22:15:23 -05:00
/* This function check for whether the ISR is done, if not, block until semaphore given.
*/
2018-10-22 03:25:41 -04:00
static inline SPI_MASTER_ISR_ATTR esp_err_t device_wait_for_isr_idle ( spi_device_t * handle , TickType_t wait )
2018-01-30 22:15:23 -05:00
{
//quickly skip if the isr is already free
if ( ! handle - > host - > isr_free ) {
// Clear the semaphore before checking
xSemaphoreTake ( handle - > semphr_polling , 0 ) ;
if ( ! handle - > host - > isr_free ) {
//block until the the isr is free and give us the semaphore
BaseType_t ret = xSemaphoreTake ( handle - > semphr_polling , wait ) ;
//TODO: add timeout handling here.
if ( ret = = pdFALSE ) return ESP_ERR_TIMEOUT ;
2017-01-06 01:20:32 -05:00
}
}
2018-01-30 22:15:23 -05:00
return ESP_OK ;
}
/* This function release the bus acquired by device_acquire_internal.
And it also tries to help other device to acquire the bus .
If the bus acquring is not needed , it goes through all device queues to see whether to invoke the ISR
*/
static SPI_MASTER_ISR_ATTR void device_release_bus_internal ( spi_host_t * host )
{
//release the bus
atomic_store ( & host - > acquire_cs , NO_CS ) ;
//first try to restore the acquiring device
for ( int i = 0 ; i < NO_CS ; i + + ) {
//search for all registered devices
spi_device_t * dev = atomic_load ( & host - > device [ i ] ) ;
if ( dev & & dev - > waiting ) {
int no_cs = NO_CS ;
atomic_compare_exchange_weak ( & host - > acquire_cs , & no_cs , i ) ;
if ( atomic_load ( & host - > acquire_cs ) = = i ) {
// Success to acquire for new device
BaseType_t ret = uxQueueMessagesWaiting ( dev - > trans_queue ) ;
if ( ret > 0 ) {
// If there are transactions in the queue, the ISR should be invoked first
// Resume the interrupt to send the task a signal
spi_isr_invoke ( dev ) ;
} else {
// Otherwise resume the task directly.
xSemaphoreGive ( dev - > semphr_polling ) ;
}
2017-01-06 01:20:32 -05:00
}
2018-01-30 22:15:23 -05:00
return ;
2017-01-06 01:20:32 -05:00
}
2018-01-30 22:15:23 -05:00
}
//if no devices waiting, searching in device queues to see whether to recover the ISR
for ( int i = 0 ; i < NO_CS ; i + + ) {
spi_device_t * dev = atomic_load ( & host - > device [ i ] ) ;
if ( dev = = NULL ) continue ;
BaseType_t ret = uxQueueMessagesWaiting ( dev - > trans_queue ) ;
if ( ret ! = 0 ) {
spi_isr_invoke ( dev ) ;
return ;
2017-01-06 01:20:32 -05:00
}
2018-01-30 22:15:23 -05:00
}
}
2017-01-06 01:20:32 -05:00
2018-10-22 03:25:41 -04:00
static inline SPI_MASTER_ISR_ATTR bool device_is_polling ( spi_device_t * handle )
2018-01-30 22:15:23 -05:00
{
return atomic_load ( & handle - > host - > acquire_cs ) = = handle - > id & & handle - > host - > polling ;
}
/*-----------------------------------------------------------------------------
Working Functions
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
// The function is called to send a new transaction, in ISR or in the task.
// Setup the transaction-specified registers and linked-list used by the DMA (or FIFO if DMA is not used)
static void SPI_MASTER_ISR_ATTR spi_new_trans ( spi_device_t * dev , spi_trans_priv_t * trans_buf )
{
spi_transaction_t * trans = NULL ;
spi_host_t * host = dev - > host ;
int dev_id = dev - > id ;
//clear int bit
host - > hw - > slave . trans_done = 0 ;
trans = trans_buf - > trans ;
host - > cur_cs = dev_id ;
//We should be done with the transmission.
assert ( host - > hw - > cmd . usr = = 0 ) ;
//Reconfigure according to device settings, the function only has effect when the dev_id is changed.
spi_setup_device ( host , dev_id ) ;
//Reset DMA peripheral
host - > hw - > dma_conf . val | = SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST ;
host - > hw - > dma_out_link . start = 0 ;
host - > hw - > dma_in_link . start = 0 ;
host - > hw - > dma_conf . val & = ~ ( SPI_OUT_RST | SPI_IN_RST | SPI_AHBM_RST | SPI_AHBM_FIFO_RST ) ;
host - > hw - > dma_conf . out_data_burst_en = 1 ;
host - > hw - > dma_conf . indscr_burst_en = 1 ;
host - > hw - > dma_conf . outdscr_burst_en = 1 ;
//Set up QIO/DIO if needed
host - > hw - > ctrl . val & = ~ ( SPI_FREAD_DUAL | SPI_FREAD_QUAD | SPI_FREAD_DIO | SPI_FREAD_QIO ) ;
host - > hw - > user . val & = ~ ( SPI_FWRITE_DUAL | SPI_FWRITE_QUAD | SPI_FWRITE_DIO | SPI_FWRITE_QIO ) ;
if ( trans - > flags & SPI_TRANS_MODE_DIO ) {
if ( trans - > flags & SPI_TRANS_MODE_DIOQIO_ADDR ) {
host - > hw - > ctrl . fread_dio = 1 ;
host - > hw - > user . fwrite_dio = 1 ;
2017-01-06 01:20:32 -05:00
} else {
2018-01-30 22:15:23 -05:00
host - > hw - > ctrl . fread_dual = 1 ;
host - > hw - > user . fwrite_dual = 1 ;
2017-01-06 01:20:32 -05:00
}
2018-01-30 22:15:23 -05:00
host - > hw - > ctrl . fastrd_mode = 1 ;
} else if ( trans - > flags & SPI_TRANS_MODE_QIO ) {
if ( trans - > flags & SPI_TRANS_MODE_DIOQIO_ADDR ) {
host - > hw - > ctrl . fread_qio = 1 ;
host - > hw - > user . fwrite_qio = 1 ;
} else {
host - > hw - > ctrl . fread_quad = 1 ;
host - > hw - > user . fwrite_quad = 1 ;
}
host - > hw - > ctrl . fastrd_mode = 1 ;
}
2017-01-06 01:20:32 -05:00
2018-01-30 22:15:23 -05:00
//Fill DMA descriptors
int extra_dummy = 0 ;
if ( trans_buf - > buffer_to_rcv ) {
if ( host - > dma_chan = = 0 ) {
//No need to setup anything; we'll copy the result out of the work registers directly later.
} else {
spicommon_setup_dma_desc_links ( host - > dmadesc_rx , ( ( trans - > rxlength + 7 ) / 8 ) , ( uint8_t * ) trans_buf - > buffer_to_rcv , true ) ;
host - > hw - > dma_in_link . addr = ( int ) ( & host - > dmadesc_rx [ 0 ] ) & 0xFFFFF ;
host - > hw - > dma_in_link . start = 1 ;
}
//when no_dummy is not set and in half-duplex mode, sets the dummy bit if RX phase exist
if ( ( ( dev - > cfg . flags & SPI_DEVICE_NO_DUMMY ) = = 0 ) & & ( dev - > cfg . flags & SPI_DEVICE_HALFDUPLEX ) ) {
extra_dummy = dev - > clk_cfg . dummy_num ;
}
} else {
//DMA temporary workaround: let RX DMA work somehow to avoid the issue in ESP32 v0/v1 silicon
if ( host - > dma_chan ! = 0 ) {
host - > hw - > dma_in_link . addr = 0 ;
host - > hw - > dma_in_link . start = 1 ;
}
}
2017-09-28 08:19:18 -04:00
2018-01-30 22:15:23 -05:00
if ( trans_buf - > buffer_to_send ) {
if ( host - > dma_chan = = 0 ) {
//Need to copy data to registers manually
for ( int x = 0 ; x < trans - > length ; x + = 32 ) {
//Use memcpy to get around alignment issues for txdata
uint32_t word ;
memcpy ( & word , & trans_buf - > buffer_to_send [ x / 32 ] , 4 ) ;
host - > hw - > data_buf [ ( x / 32 ) ] = word ;
2017-01-06 01:20:32 -05:00
}
2018-01-30 22:15:23 -05:00
} else {
spicommon_setup_dma_desc_links ( host - > dmadesc_tx , ( trans - > length + 7 ) / 8 , ( uint8_t * ) trans_buf - > buffer_to_send , false ) ;
host - > hw - > dma_out_link . addr = ( int ) ( & host - > dmadesc_tx [ 0 ] ) & 0xFFFFF ;
host - > hw - > dma_out_link . start = 1 ;
2017-01-06 01:20:32 -05:00
}
2018-01-30 22:15:23 -05:00
}
2017-07-16 23:37:32 -04:00
2018-01-30 22:15:23 -05:00
//SPI iface needs to be configured for a delay in some cases.
//configure dummy bits
host - > hw - > user . usr_dummy = ( dev - > cfg . dummy_bits + extra_dummy ) ? 1 : 0 ;
host - > hw - > user1 . usr_dummy_cyclelen = dev - > cfg . dummy_bits + extra_dummy - 1 ;
2018-03-19 23:33:42 -04:00
2018-01-30 22:15:23 -05:00
int miso_long_delay = 0 ;
if ( dev - > clk_cfg . miso_delay < 0 ) {
//if the data comes too late, delay half a SPI clock to improve reading
miso_long_delay = 1 ;
host - > hw - > ctrl2 . miso_delay_num = 0 ;
} else {
//if the data is so fast that dummy_bit is used, delay some apb clocks to meet the timing
host - > hw - > ctrl2 . miso_delay_num = extra_dummy ? dev - > clk_cfg . miso_delay : 0 ;
}
2017-09-28 08:19:18 -04:00
2018-01-30 22:15:23 -05:00
if ( miso_long_delay ) {
switch ( dev - > cfg . mode ) {
case 0 :
host - > hw - > ctrl2 . miso_delay_mode = 2 ;
break ;
case 1 :
host - > hw - > ctrl2 . miso_delay_mode = 1 ;
break ;
case 2 :
host - > hw - > ctrl2 . miso_delay_mode = 1 ;
break ;
case 3 :
host - > hw - > ctrl2 . miso_delay_mode = 2 ;
break ;
2017-09-28 08:19:18 -04:00
}
2018-01-30 22:15:23 -05:00
} else {
host - > hw - > ctrl2 . miso_delay_mode = 0 ;
}
2017-09-28 08:19:18 -04:00
2018-01-30 22:15:23 -05:00
host - > hw - > mosi_dlen . usr_mosi_dbitlen = trans - > length - 1 ;
if ( dev - > cfg . flags & SPI_DEVICE_HALFDUPLEX ) {
host - > hw - > miso_dlen . usr_miso_dbitlen = trans - > rxlength - 1 ;
} else {
//rxlength is not used in full-duplex mode
host - > hw - > miso_dlen . usr_miso_dbitlen = trans - > length - 1 ;
}
//Configure bit sizes, load addr and command
int cmdlen ;
int addrlen ;
if ( ! ( dev - > cfg . flags & SPI_DEVICE_HALFDUPLEX ) & & dev - > cfg . cs_ena_pretrans ! = 0 ) {
/* The command and address phase is not compatible with cs_ena_pretrans
* in full duplex mode .
*/
cmdlen = 0 ;
addrlen = 0 ;
} else {
if ( trans - > flags & SPI_TRANS_VARIABLE_CMD ) {
cmdlen = ( ( spi_transaction_ext_t * ) trans ) - > command_bits ;
2017-08-01 10:16:41 -04:00
} else {
2018-01-30 22:15:23 -05:00
cmdlen = dev - > cfg . command_bits ;
2017-08-01 10:16:41 -04:00
}
2018-01-30 22:15:23 -05:00
if ( trans - > flags & SPI_TRANS_VARIABLE_ADDR ) {
addrlen = ( ( spi_transaction_ext_t * ) trans ) - > address_bits ;
2017-09-27 08:16:37 -04:00
} else {
2018-01-30 22:15:23 -05:00
addrlen = dev - > cfg . address_bits ;
2017-09-27 08:16:37 -04:00
}
2018-01-30 22:15:23 -05:00
}
host - > hw - > user1 . usr_addr_bitlen = addrlen - 1 ;
host - > hw - > user2 . usr_command_bitlen = cmdlen - 1 ;
host - > hw - > user . usr_addr = addrlen ? 1 : 0 ;
host - > hw - > user . usr_command = cmdlen ? 1 : 0 ;
2018-09-27 23:36:14 -04:00
if ( ( dev - > cfg . flags & SPI_DEVICE_TXBIT_LSBFIRST ) = = 0 ) {
/* Output command will be sent from bit 7 to 0 of command_value, and
* then bit 15 to 8 of the same register field . Shift and swap to send
* more straightly .
*/
host - > hw - > user2 . usr_command_value = SPI_SWAP_DATA_TX ( trans - > cmd , cmdlen ) ;
// shift the address to MSB of addr (and maybe slv_wr_status) register.
// output address will be sent from MSB to LSB of addr register, then comes the MSB to LSB of slv_wr_status register.
if ( addrlen > 32 ) {
host - > hw - > addr = trans - > addr > > ( addrlen - 32 ) ;
host - > hw - > slv_wr_status = trans - > addr < < ( 64 - addrlen ) ;
} else {
host - > hw - > addr = trans - > addr < < ( 32 - addrlen ) ;
}
2018-01-30 22:15:23 -05:00
} else {
2018-09-27 23:36:14 -04:00
/* The output command start from bit0 to bit 15, kept as is.
* The output address start from the LSB of the highest byte , i . e .
* addr [ 24 ] - > addr [ 31 ]
* . . .
* addr [ 0 ] - > addr [ 7 ]
* slv_wr_status [ 24 ] - > slv_wr_status [ 31 ]
* . . .
* slv_wr_status [ 0 ] - > slv_wr_status [ 7 ]
* So swap the byte order to let the LSB sent first .
*/
host - > hw - > user2 . usr_command_value = trans - > cmd ;
uint64_t addr = __builtin_bswap64 ( trans - > addr ) ;
host - > hw - > addr = addr > > 32 ;
host - > hw - > slv_wr_status = addr ;
2018-01-30 22:15:23 -05:00
}
2018-06-19 08:38:17 -04:00
2018-01-30 22:15:23 -05:00
if ( ( ! ( dev - > cfg . flags & SPI_DEVICE_HALFDUPLEX ) & & trans_buf - > buffer_to_rcv ) | |
trans_buf - > buffer_to_send ) {
host - > hw - > user . usr_mosi = 1 ;
} else {
host - > hw - > user . usr_mosi = 0 ;
}
host - > hw - > user . usr_miso = ( trans_buf - > buffer_to_rcv ) ? 1 : 0 ;
2017-09-27 08:16:37 -04:00
2018-01-30 22:15:23 -05:00
//Call pre-transmission callback, if any
if ( dev - > cfg . pre_cb ) dev - > cfg . pre_cb ( trans ) ;
//Kick off transfer
host - > hw - > cmd . usr = 1 ;
}
2018-06-27 06:41:57 -04:00
2018-01-30 22:15:23 -05:00
// The function is called when a transaction is done, in ISR or in the task.
// Fetch the data from FIFO and call the ``post_cb``.
static void SPI_MASTER_ISR_ATTR spi_post_trans ( spi_host_t * host )
{
spi_transaction_t * cur_trans = host - > cur_trans_buf . trans ;
if ( host - > cur_trans_buf . buffer_to_rcv & & host - > dma_chan = = 0 ) {
//Need to copy from SPI regs to result buffer.
for ( int x = 0 ; x < cur_trans - > rxlength ; x + = 32 ) {
//Do a memcpy to get around possible alignment issues in rx_buffer
uint32_t word = host - > hw - > data_buf [ x / 32 ] ;
int len = cur_trans - > rxlength - x ;
if ( len > 32 ) len = 32 ;
memcpy ( & host - > cur_trans_buf . buffer_to_rcv [ x / 32 ] , & word , ( len + 7 ) / 8 ) ;
2017-01-06 01:20:32 -05:00
}
2018-01-30 22:15:23 -05:00
}
//Call post-transaction callback, if any
spi_device_t * dev = atomic_load ( & host - > device [ host - > cur_cs ] ) ;
if ( dev - > cfg . post_cb ) dev - > cfg . post_cb ( cur_trans ) ;
host - > cur_cs = NO_CS ;
}
// This is run in interrupt context.
static void SPI_MASTER_ISR_ATTR spi_intr ( void * arg )
{
int i ;
BaseType_t r ;
BaseType_t do_yield = pdFALSE ;
spi_host_t * host = ( spi_host_t * ) arg ;
2017-09-27 08:16:37 -04:00
2018-01-30 22:15:23 -05:00
assert ( host - > hw - > slave . trans_done = = 1 ) ;
2017-01-06 01:20:32 -05:00
2018-01-30 22:15:23 -05:00
/*------------ deal with the in-flight transaction -----------------*/
if ( host - > cur_cs ! = NO_CS ) {
//Okay, transaction is done.
const int cs = host - > cur_cs ;
//Tell common code DMA workaround that our DMA channel is idle. If needed, the code will do a DMA reset.
if ( host - > dma_chan ) {
spicommon_dmaworkaround_idle ( host - > dma_chan ) ;
}
//cur_cs is changed to NO_CS here
spi_post_trans ( host ) ;
//Return transaction descriptor.
xQueueSendFromISR ( atomic_load ( & host - > device [ cs ] ) - > ret_queue , & host - > cur_trans_buf , & do_yield ) ;
# ifdef CONFIG_PM_ENABLE
//Release APB frequency lock
esp_pm_lock_release ( host - > pm_lock ) ;
# endif
}
/*------------ new transaction starts here ------------------*/
assert ( host - > cur_cs = = NO_CS ) ;
// Clear isr_free before the checking of acquire_cs so that the task will always block if we find the bus is not acquired.
// Small possiblility that the task is blocked but we find the bus is acquired.
host - > isr_free = false ;
/* When the bus is not occupied, the task uses esp_intr_enable to inform the ISR there's new transaction.
* If the queue is empty , we disable the system interrupt .
* We disable this first , to avoid the conflict when the task enable and the ISR disable at the same time
* If the transaction is sent ( queue not empty ) , we will re - ebale it ( see below ) .
*/
esp_intr_disable ( host - > intr ) ;
int acquire_cs = atomic_load ( & host - > acquire_cs ) ;
if ( acquire_cs ! = NO_CS ) {
// Only look in the queue of the occupying device.
i = acquire_cs ;
spi_device_t * dev = atomic_load ( & host - > device [ i ] ) ;
assert ( dev ) ;
r = xQueueReceiveFromISR ( dev - > trans_queue , & host - > cur_trans_buf , & do_yield ) ;
// If the Queue is empty, skip the sending by setting i=NO_CS
// Otherwise i is kept as is and the transaction will be sent.
if ( ! r ) {
// Set the free to true before resume the task
host - > isr_free = true ;
xSemaphoreGiveFromISR ( dev - > semphr_polling , & do_yield ) ;
i = NO_CS ;
}
} else {
//Go through all device queues to find a transaction to send
//ToDo: This is a stupidly simple low-cs-first priority scheme. Make this configurable somehow. - JD
for ( i = 0 ; i < NO_CS ; i + + ) {
spi_device_t * dev = atomic_load ( & host - > device [ i ] ) ;
if ( dev ) {
r = xQueueReceiveFromISR ( dev - > trans_queue , & host - > cur_trans_buf , & do_yield ) ;
//Stop looking if we have a transaction to send.
if ( r ) break ;
}
}
if ( i = = NO_CS ) {
host - > isr_free = true ;
}
}
// Actually send the transaction
if ( i ! = NO_CS ) {
spi_trans_priv_t * const cur_trans_buf = & host - > cur_trans_buf ;
if ( host - > dma_chan ! = 0 & & ( cur_trans_buf - > buffer_to_rcv | | cur_trans_buf - > buffer_to_send ) ) {
//mark channel as active, so that the DMA will not be reset by the slave
spicommon_dmaworkaround_transfer_active ( host - > dma_chan ) ;
}
spi_new_trans ( atomic_load ( & host - > device [ i ] ) , cur_trans_buf ) ;
//re-enable interrupt disabled above
esp_intr_enable ( host - > intr ) ;
2017-01-06 01:20:32 -05:00
}
if ( do_yield ) portYIELD_FROM_ISR ( ) ;
}
2018-10-22 03:25:41 -04:00
static SPI_MASTER_ISR_ATTR esp_err_t check_trans_valid ( spi_device_handle_t handle , spi_transaction_t * trans_desc )
2017-01-06 01:20:32 -05:00
{
SPI_CHECK ( handle ! = NULL , " invalid dev handle " , ESP_ERR_INVALID_ARG ) ;
2018-01-30 22:15:23 -05:00
spi_host_t * host = handle - > host ;
2017-09-28 08:19:18 -04:00
//check transmission length
2017-07-16 23:37:32 -04:00
SPI_CHECK ( ( trans_desc - > flags & SPI_TRANS_USE_RXDATA ) = = 0 | | trans_desc - > rxlength < = 32 , " rxdata transfer > 32 bits without configured DMA " , ESP_ERR_INVALID_ARG ) ;
SPI_CHECK ( ( trans_desc - > flags & SPI_TRANS_USE_TXDATA ) = = 0 | | trans_desc - > length < = 32 , " txdata transfer > 32 bits without configured DMA " , ESP_ERR_INVALID_ARG ) ;
2017-03-31 03:05:25 -04:00
SPI_CHECK ( trans_desc - > length < = handle - > host - > max_transfer_sz * 8 , " txdata transfer > host maximum " , ESP_ERR_INVALID_ARG ) ;
SPI_CHECK ( trans_desc - > rxlength < = handle - > host - > max_transfer_sz * 8 , " rxdata transfer > host maximum " , ESP_ERR_INVALID_ARG ) ;
2017-08-01 10:16:41 -04:00
SPI_CHECK ( ( handle - > cfg . flags & SPI_DEVICE_HALFDUPLEX ) | | trans_desc - > rxlength < = trans_desc - > length , " rx length > tx length in full duplex mode " , ESP_ERR_INVALID_ARG ) ;
2017-09-28 08:19:18 -04:00
//check working mode
2017-08-14 23:00:33 -04:00
SPI_CHECK ( ! ( ( trans_desc - > flags & ( SPI_TRANS_MODE_DIO | SPI_TRANS_MODE_QIO ) ) & & ( handle - > cfg . flags & SPI_DEVICE_3WIRE ) ) , " incompatible iface params " , ESP_ERR_INVALID_ARG ) ;
SPI_CHECK ( ! ( ( trans_desc - > flags & ( SPI_TRANS_MODE_DIO | SPI_TRANS_MODE_QIO ) ) & & ( ! ( handle - > cfg . flags & SPI_DEVICE_HALFDUPLEX ) ) ) , " incompatible iface params " , ESP_ERR_INVALID_ARG ) ;
2018-01-30 22:15:23 -05:00
SPI_CHECK ( ! ( handle - > cfg . flags & SPI_DEVICE_HALFDUPLEX ) | | host - > dma_chan = = 0 | | ! ( trans_desc - > flags & SPI_TRANS_USE_RXDATA | | trans_desc - > rx_buffer ! = NULL )
2017-08-14 23:00:33 -04:00
| | ! ( trans_desc - > flags & SPI_TRANS_USE_TXDATA | | trans_desc - > tx_buffer ! = NULL ) , " SPI half duplex mode does not support using DMA with both MOSI and MISO phases. " , ESP_ERR_INVALID_ARG ) ;
//In Full duplex mode, default rxlength to be the same as length, if not filled in.
2017-07-16 23:37:32 -04:00
// set rxlength to length is ok, even when rx buffer=NULL
2017-08-14 23:00:33 -04:00
if ( trans_desc - > rxlength = = 0 & & ! ( handle - > cfg . flags & SPI_DEVICE_HALFDUPLEX ) ) {
2017-07-16 23:37:32 -04:00
trans_desc - > rxlength = trans_desc - > length ;
}
2018-01-30 22:15:23 -05:00
return ESP_OK ;
}
2018-10-22 03:25:41 -04:00
static SPI_MASTER_ISR_ATTR void uninstall_priv_desc ( spi_trans_priv_t * trans_buf )
2018-01-30 22:15:23 -05:00
{
spi_transaction_t * trans_desc = trans_buf - > trans ;
if ( ( void * ) trans_buf - > buffer_to_send ! = & trans_desc - > tx_data [ 0 ] & &
trans_buf - > buffer_to_send ! = trans_desc - > tx_buffer ) {
free ( ( void * ) trans_buf - > buffer_to_send ) ; //force free, ignore const
}
//copy data from temporary DMA-capable buffer back to IRAM buffer and free the temporary one.
if ( ( void * ) trans_buf - > buffer_to_rcv ! = & trans_desc - > rx_data [ 0 ] & &
trans_buf - > buffer_to_rcv ! = trans_desc - > rx_buffer ) {
if ( trans_desc - > flags & SPI_TRANS_USE_RXDATA ) {
memcpy ( ( uint8_t * ) & trans_desc - > rx_data [ 0 ] , trans_buf - > buffer_to_rcv , ( trans_desc - > rxlength + 7 ) / 8 ) ;
} else {
memcpy ( trans_desc - > rx_buffer , trans_buf - > buffer_to_rcv , ( trans_desc - > rxlength + 7 ) / 8 ) ;
}
free ( trans_buf - > buffer_to_rcv ) ;
}
}
2018-10-22 03:25:41 -04:00
static SPI_MASTER_ISR_ATTR esp_err_t setup_priv_desc ( spi_transaction_t * trans_desc , spi_trans_priv_t * new_desc , bool isdma )
2018-01-30 22:15:23 -05:00
{
* new_desc = ( spi_trans_priv_t ) { . trans = trans_desc , } ;
2017-07-16 23:37:32 -04:00
// rx memory assign
2018-01-30 22:15:23 -05:00
uint32_t * rcv_ptr ;
2017-07-16 23:37:32 -04:00
if ( trans_desc - > flags & SPI_TRANS_USE_RXDATA ) {
2018-01-30 22:15:23 -05:00
rcv_ptr = ( uint32_t * ) & trans_desc - > rx_data [ 0 ] ;
2017-09-28 08:19:18 -04:00
} else {
2017-07-16 23:37:32 -04:00
//if not use RXDATA neither rx_buffer, buffer_to_rcv assigned to NULL
2018-01-30 22:15:23 -05:00
rcv_ptr = trans_desc - > rx_buffer ;
2017-07-16 23:37:32 -04:00
}
2018-01-30 22:15:23 -05:00
if ( rcv_ptr & & isdma & & ( ! esp_ptr_dma_capable ( rcv_ptr ) | | ( ( int ) rcv_ptr % 4 ! = 0 ) ) ) {
2017-09-15 04:19:11 -04:00
//if rxbuf in the desc not DMA-capable, malloc a new one. The rx buffer need to be length of multiples of 32 bits to avoid heap corruption.
2018-01-30 22:15:23 -05:00
ESP_LOGI ( SPI_TAG , " Allocate RX buffer for DMA " ) ;
rcv_ptr = heap_caps_malloc ( ( trans_desc - > rxlength + 31 ) / 8 , MALLOC_CAP_DMA ) ;
if ( rcv_ptr = = NULL ) goto clean_up ;
2017-07-16 23:37:32 -04:00
}
2018-01-30 22:15:23 -05:00
new_desc - > buffer_to_rcv = rcv_ptr ;
2017-09-28 08:19:18 -04:00
2017-07-16 23:37:32 -04:00
// tx memory assign
2018-01-30 22:15:23 -05:00
const uint32_t * send_ptr ;
2017-07-16 23:37:32 -04:00
if ( trans_desc - > flags & SPI_TRANS_USE_TXDATA ) {
2018-01-30 22:15:23 -05:00
send_ptr = ( uint32_t * ) & trans_desc - > tx_data [ 0 ] ;
2017-09-28 08:19:18 -04:00
} else {
2017-07-16 23:37:32 -04:00
//if not use TXDATA neither tx_buffer, tx data assigned to NULL
2018-01-30 22:15:23 -05:00
send_ptr = trans_desc - > tx_buffer ;
2017-07-16 23:37:32 -04:00
}
2018-01-30 22:15:23 -05:00
if ( send_ptr & & isdma & & ! esp_ptr_dma_capable ( send_ptr ) ) {
2017-07-16 23:37:32 -04:00
//if txbuf in the desc not DMA-capable, malloc a new one
2018-01-30 22:15:23 -05:00
ESP_LOGI ( SPI_TAG , " Allocate TX buffer for DMA " ) ;
uint32_t * temp = heap_caps_malloc ( ( trans_desc - > length + 7 ) / 8 , MALLOC_CAP_DMA ) ;
if ( temp = = NULL ) goto clean_up ;
memcpy ( temp , send_ptr , ( trans_desc - > length + 7 ) / 8 ) ;
send_ptr = temp ;
2017-07-16 23:37:32 -04:00
}
2018-01-30 22:15:23 -05:00
new_desc - > buffer_to_send = send_ptr ;
return ESP_OK ;
clean_up :
uninstall_priv_desc ( new_desc ) ;
return ESP_ERR_NO_MEM ;
}
esp_err_t SPI_MASTER_ATTR spi_device_queue_trans ( spi_device_handle_t handle , spi_transaction_t * trans_desc , TickType_t ticks_to_wait )
{
esp_err_t ret = check_trans_valid ( handle , trans_desc ) ;
if ( ret ! = ESP_OK ) return ret ;
spi_host_t * host = handle - > host ;
SPI_CHECK ( ! device_is_polling ( handle ) , " Cannot queue new transaction while previous polling transaction is not terminated. " , ESP_ERR_INVALID_STATE ) ;
spi_trans_priv_t trans_buf ;
ret = setup_priv_desc ( trans_desc , & trans_buf , ( host - > dma_chan ! = 0 ) ) ;
if ( ret ! = ESP_OK ) return ret ;
2017-09-28 08:19:18 -04:00
2017-09-24 02:59:15 -04:00
# ifdef CONFIG_PM_ENABLE
2018-01-30 22:15:23 -05:00
esp_pm_lock_acquire ( host - > pm_lock ) ;
2017-09-24 02:59:15 -04:00
# endif
2018-01-30 22:15:23 -05:00
//Send to queue and invoke the ISR.
BaseType_t r = xQueueSend ( handle - > trans_queue , ( void * ) & trans_buf , ticks_to_wait ) ;
2017-11-16 22:25:44 -05:00
if ( ! r ) {
ret = ESP_ERR_TIMEOUT ;
# ifdef CONFIG_PM_ENABLE
//Release APB frequency lock
2018-01-30 22:15:23 -05:00
esp_pm_lock_release ( host - > pm_lock ) ;
2017-11-16 22:25:44 -05:00
# endif
goto clean_up ;
}
2018-01-30 22:15:23 -05:00
spi_isr_invoke ( handle ) ;
2017-01-06 01:20:32 -05:00
return ESP_OK ;
2017-11-16 22:25:44 -05:00
clean_up :
2018-01-30 22:15:23 -05:00
uninstall_priv_desc ( & trans_buf ) ;
2017-11-16 22:25:44 -05:00
return ret ;
2017-01-06 01:20:32 -05:00
}
2018-06-11 07:54:18 -04:00
esp_err_t SPI_MASTER_ATTR spi_device_get_trans_result ( spi_device_handle_t handle , spi_transaction_t * * trans_desc , TickType_t ticks_to_wait )
2017-01-06 01:20:32 -05:00
{
BaseType_t r ;
2018-01-30 22:15:23 -05:00
spi_trans_priv_t trans_buf ;
2017-01-06 01:20:32 -05:00
SPI_CHECK ( handle ! = NULL , " invalid dev handle " , ESP_ERR_INVALID_ARG ) ;
2018-01-30 22:15:23 -05:00
//use the interrupt, block until return
2017-07-16 23:37:32 -04:00
r = xQueueReceive ( handle - > ret_queue , ( void * ) & trans_buf , ticks_to_wait ) ;
if ( ! r ) {
// The memory occupied by rx and tx DMA buffer destroyed only when receiving from the queue (transaction finished).
2017-09-28 08:19:18 -04:00
// If timeout, wait and retry.
2018-01-30 22:15:23 -05:00
// Every in-flight transaction request occupies internal memory as DMA buffer if needed.
2017-07-16 23:37:32 -04:00
return ESP_ERR_TIMEOUT ;
}
2018-01-30 22:15:23 -05:00
//release temporary buffers
uninstall_priv_desc ( & trans_buf ) ;
2017-07-16 23:37:32 -04:00
( * trans_desc ) = trans_buf . trans ;
2017-01-06 01:20:32 -05:00
return ESP_OK ;
}
//Porcelain to do one blocking transmission.
2018-06-11 07:54:18 -04:00
esp_err_t SPI_MASTER_ATTR spi_device_transmit ( spi_device_handle_t handle , spi_transaction_t * trans_desc )
2017-01-06 01:20:32 -05:00
{
esp_err_t ret ;
spi_transaction_t * ret_trans ;
//ToDo: check if any spi transfers in flight
2018-01-30 22:15:23 -05:00
ret = spi_device_queue_trans ( handle , trans_desc , portMAX_DELAY ) ;
if ( ret ! = ESP_OK ) return ret ;
ret = spi_device_get_trans_result ( handle , & ret_trans , portMAX_DELAY ) ;
if ( ret ! = ESP_OK ) return ret ;
assert ( ret_trans = = trans_desc ) ;
return ESP_OK ;
}
esp_err_t SPI_MASTER_ATTR spi_device_acquire_bus ( spi_device_t * device , TickType_t wait )
{
spi_host_t * const host = device - > host ;
SPI_CHECK ( wait = = portMAX_DELAY , " acquire finite time not supported now. " , ESP_ERR_INVALID_ARG ) ;
SPI_CHECK ( ! device_is_polling ( device ) , " Cannot acquire bus when a polling transaction is in progress. " , ESP_ERR_INVALID_STATE ) ;
esp_err_t ret = device_acquire_bus_internal ( device , portMAX_DELAY ) ;
if ( ret ! = ESP_OK ) return ret ;
ret = device_wait_for_isr_idle ( device , portMAX_DELAY ) ;
if ( ret ! = ESP_OK ) return ret ;
device - > host - > bus_locked = true ;
ESP_LOGD ( SPI_TAG , " device%d acquired the bus " , device - > id ) ;
# ifdef CONFIG_PM_ENABLE
// though we don't suggest to block the task before ``release_bus``, still allow doing so.
// this keeps the spi clock at 80MHz even if all tasks are blocked
esp_pm_lock_acquire ( device - > host - > pm_lock ) ;
# endif
//configure the device so that we don't need to do it again in the following transactions
spi_setup_device ( host , device - > id ) ;
//the DMA is also occupied by the device, all the slave devices that using DMA should wait until bus released.
if ( host - > dma_chan ! = 0 ) {
spicommon_dmaworkaround_transfer_active ( host - > dma_chan ) ;
}
return ESP_OK ;
}
// This function restore configurations required in the non-polling mode
void SPI_MASTER_ATTR spi_device_release_bus ( spi_device_t * dev )
{
spi_host_t * host = dev - > host ;
if ( device_is_polling ( dev ) ) {
ESP_LOGE ( SPI_TAG , " Cannot release bus when a polling transaction is in progress. " ) ;
assert ( 0 ) ;
}
if ( host - > dma_chan ! = 0 ) {
spicommon_dmaworkaround_idle ( host - > dma_chan ) ;
}
//Tell common code DMA workaround that our DMA channel is idle. If needed, the code will do a DMA reset.
//allow clock to be lower than 80MHz when all tasks blocked
# ifdef CONFIG_PM_ENABLE
//Release APB frequency lock
esp_pm_lock_release ( host - > pm_lock ) ;
# endif
ESP_LOGD ( SPI_TAG , " device%d release bus " , dev - > id ) ;
dev - > host - > bus_locked = false ;
device_release_bus_internal ( dev - > host ) ;
}
esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_start ( spi_device_handle_t handle , spi_transaction_t * trans_desc , TickType_t ticks_to_wait )
{
esp_err_t ret ;
SPI_CHECK ( ticks_to_wait = = portMAX_DELAY , " currently timeout is not available for polling transactions " , ESP_ERR_INVALID_ARG ) ;
spi_host_t * host = handle - > host ;
ret = check_trans_valid ( handle , trans_desc ) ;
2017-01-06 01:20:32 -05:00
if ( ret ! = ESP_OK ) return ret ;
2018-01-30 22:15:23 -05:00
SPI_CHECK ( ! device_is_polling ( handle ) , " Cannot send polling transaction while the previous polling transaction is not terminated. " , ESP_ERR_INVALID_STATE ) ;
ret = setup_priv_desc ( trans_desc , & host - > cur_trans_buf , ( handle - > host - > dma_chan ! = 0 ) ) ;
2017-01-06 01:20:32 -05:00
if ( ret ! = ESP_OK ) return ret ;
2018-01-30 22:15:23 -05:00
device_acquire_bus_internal ( handle , portMAX_DELAY ) ;
device_wait_for_isr_idle ( handle , portMAX_DELAY ) ;
assert ( atomic_load ( & host - > acquire_cs ) = = handle - > id ) ;
assert ( host - > isr_free ) ;
//Polling, no interrupt is used.
host - > polling = true ;
ESP_LOGV ( SPI_TAG , " polling trans " ) ;
spi_new_trans ( handle , & host - > cur_trans_buf ) ;
return ESP_OK ;
}
esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_end ( spi_device_handle_t handle , TickType_t ticks_to_wait )
{
SPI_CHECK ( handle ! = NULL , " invalid dev handle " , ESP_ERR_INVALID_ARG ) ;
spi_host_t * host = handle - > host ;
//if (host->acquire_cs == handle->id && host->polling) {
assert ( host - > cur_cs = = atomic_load ( & host - > acquire_cs ) ) ;
TickType_t start = xTaskGetTickCount ( ) ;
while ( ! host - > hw - > slave . trans_done ) {
TickType_t end = xTaskGetTickCount ( ) ;
if ( end - start > ticks_to_wait ) {
return ESP_ERR_TIMEOUT ;
}
}
ESP_LOGV ( SPI_TAG , " polling trans done " ) ;
//deal with the in-flight transaction
spi_post_trans ( host ) ;
//release temporary buffers
uninstall_priv_desc ( & host - > cur_trans_buf ) ;
host - > polling = false ;
if ( ! host - > bus_locked ) {
device_release_bus_internal ( host ) ;
}
return ESP_OK ;
}
esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_transmit ( spi_device_handle_t handle , spi_transaction_t * trans_desc )
{
esp_err_t ret ;
ret = spi_device_polling_start ( handle , trans_desc , portMAX_DELAY ) ;
if ( ret ! = ESP_OK ) return ret ;
ret = spi_device_polling_end ( handle , portMAX_DELAY ) ;
if ( ret ! = ESP_OK ) return ret ;
2017-01-06 01:20:32 -05:00
return ESP_OK ;
}
2018-01-30 22:15:23 -05:00