Files
Pico-GB/ext/FatFs_SPI/sd_driver/spi.c
T

222 lines
8.9 KiB
C

/* spi.c
Copyright 2021 Carl John Kugler III
Licensed under the Apache License, Version 2.0 (the License); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
*/
#include <stdbool.h>
//
#include "pico/stdlib.h"
#include "pico/mutex.h"
#include "pico/sem.h"
//
#include "my_debug.h"
//
#include "spi.h"
static bool irqChannel1 = false;
static bool irqShared = true;
void spi_irq_handler(spi_t *pSPI) {
if (irqChannel1) {
if (dma_hw->ints1 & 1u << pSPI->rx_dma) { // Ours?
dma_hw->ints1 = 1u << pSPI->rx_dma; // clear it
myASSERT(!dma_channel_is_busy(pSPI->rx_dma));
sem_release(&pSPI->sem);
}
} else {
if (dma_hw->ints0 & 1u << pSPI->rx_dma) { // Ours?
dma_hw->ints0 = 1u << pSPI->rx_dma; // clear it
myASSERT(!dma_channel_is_busy(pSPI->rx_dma));
sem_release(&pSPI->sem);
}
}
}
void set_spi_dma_irq_channel(bool useChannel1, bool shared) {
irqChannel1 = useChannel1;
irqShared = shared;
}
// SPI Transfer: Read & Write (simultaneously) on SPI bus
// If the data that will be received is not important, pass NULL as rx.
// If the data that will be transmitted is not important,
// pass NULL as tx and then the SPI_FILL_CHAR is sent out as each data
// element.
bool spi_transfer(spi_t *pSPI, const uint8_t *tx, uint8_t *rx, size_t length) {
// myASSERT(512 == length || 1 == length);
myASSERT(tx || rx);
// myASSERT(!(tx && rx));
// tx write increment is already false
if (tx) {
channel_config_set_read_increment(&pSPI->tx_dma_cfg, true);
} else {
static const uint8_t dummy = SPI_FILL_CHAR;
tx = &dummy;
channel_config_set_read_increment(&pSPI->tx_dma_cfg, false);
}
// rx read increment is already false
if (rx) {
channel_config_set_write_increment(&pSPI->rx_dma_cfg, true);
} else {
static uint8_t dummy = 0xA5;
rx = &dummy;
channel_config_set_write_increment(&pSPI->rx_dma_cfg, false);
}
// Clear the interrupt request.
dma_hw->ints0 = 1u << pSPI->rx_dma;
dma_channel_configure(pSPI->tx_dma, &pSPI->tx_dma_cfg,
&spi_get_hw(pSPI->hw_inst)->dr, // write address
tx, // read address
length, // element count (each element is of
// size transfer_data_size)
false); // start
dma_channel_configure(pSPI->rx_dma, &pSPI->rx_dma_cfg,
rx, // write address
&spi_get_hw(pSPI->hw_inst)->dr, // read address
length, // element count (each element is of
// size transfer_data_size)
false); // start
// start them exactly simultaneously to avoid races (in extreme cases
// the FIFO could overflow)
dma_start_channel_mask((1u << pSPI->tx_dma) | (1u << pSPI->rx_dma));
/* Timeout 1 sec */
uint32_t timeOut = 1000;
/* Wait until master completes transfer or time out has occured. */
bool rc = sem_acquire_timeout_ms(
&pSPI->sem, timeOut); // Wait for notification from ISR
if (!rc) {
// If the timeout is reached the function will return false
DBG_PRINTF("Notification wait timed out in %s\n", __FUNCTION__);
return false;
}
// Shouldn't be necessary:
dma_channel_wait_for_finish_blocking(pSPI->tx_dma);
dma_channel_wait_for_finish_blocking(pSPI->rx_dma);
myASSERT(!dma_channel_is_busy(pSPI->tx_dma));
myASSERT(!dma_channel_is_busy(pSPI->rx_dma));
return true;
}
void spi_lock(spi_t *pSPI) {
myASSERT(mutex_is_initialized(&pSPI->mutex));
mutex_enter_blocking(&pSPI->mutex);
}
void spi_unlock(spi_t *pSPI) {
myASSERT(mutex_is_initialized(&pSPI->mutex));
mutex_exit(&pSPI->mutex);
}
bool my_spi_init(spi_t *pSPI) {
auto_init_mutex(my_spi_init_mutex);
mutex_enter_blocking(&my_spi_init_mutex);
if (!pSPI->initialized) {
//// The SPI may be shared (using multiple SSs); protect it
//pSPI->mutex = xSemaphoreCreateRecursiveMutex();
//xSemaphoreTakeRecursive(pSPI->mutex, portMAX_DELAY);
if (!mutex_is_initialized(&pSPI->mutex)) mutex_init(&pSPI->mutex);
spi_lock(pSPI);
// For the IRQ notification:
sem_init(&pSPI->sem, 0, 1);
/* Configure component */
// Enable SPI at 100 kHz and connect to GPIOs
spi_init(pSPI->hw_inst, 100 * 1000);
spi_set_format(pSPI->hw_inst, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
gpio_set_function(pSPI->miso_gpio, GPIO_FUNC_SPI);
gpio_set_function(pSPI->mosi_gpio, GPIO_FUNC_SPI);
gpio_set_function(pSPI->sck_gpio, GPIO_FUNC_SPI);
// ss_gpio is initialized in sd_init_driver()
// Slew rate limiting levels for GPIO outputs.
// enum gpio_slew_rate { GPIO_SLEW_RATE_SLOW = 0, GPIO_SLEW_RATE_FAST = 1 }
// void gpio_set_slew_rate (uint gpio,enum gpio_slew_rate slew)
// Default appears to be GPIO_SLEW_RATE_SLOW.
// Drive strength levels for GPIO outputs.
// enum gpio_drive_strength { GPIO_DRIVE_STRENGTH_2MA = 0, GPIO_DRIVE_STRENGTH_4MA = 1, GPIO_DRIVE_STRENGTH_8MA = 2,
// GPIO_DRIVE_STRENGTH_12MA = 3 }
// enum gpio_drive_strength gpio_get_drive_strength (uint gpio)
if (pSPI->set_drive_strength) {
gpio_set_drive_strength(pSPI->mosi_gpio, pSPI->mosi_gpio_drive_strength);
gpio_set_drive_strength(pSPI->sck_gpio, pSPI->sck_gpio_drive_strength);
}
// SD cards' DO MUST be pulled up.
gpio_pull_up(pSPI->miso_gpio);
// Grab some unused dma channels
pSPI->tx_dma = dma_claim_unused_channel(true);
pSPI->rx_dma = dma_claim_unused_channel(true);
pSPI->tx_dma_cfg = dma_channel_get_default_config(pSPI->tx_dma);
pSPI->rx_dma_cfg = dma_channel_get_default_config(pSPI->rx_dma);
channel_config_set_transfer_data_size(&pSPI->tx_dma_cfg, DMA_SIZE_8);
channel_config_set_transfer_data_size(&pSPI->rx_dma_cfg, DMA_SIZE_8);
// We set the outbound DMA to transfer from a memory buffer to the SPI
// transmit FIFO paced by the SPI TX FIFO DREQ The default is for the
// read address to increment every element (in this case 1 byte -
// DMA_SIZE_8) and for the write address to remain unchanged.
channel_config_set_dreq(&pSPI->tx_dma_cfg, spi_get_index(pSPI->hw_inst)
? DREQ_SPI1_TX
: DREQ_SPI0_TX);
channel_config_set_write_increment(&pSPI->tx_dma_cfg, false);
// We set the inbound DMA to transfer from the SPI receive FIFO to a
// memory buffer paced by the SPI RX FIFO DREQ We coinfigure the read
// address to remain unchanged for each element, but the write address
// to increment (so data is written throughout the buffer)
channel_config_set_dreq(&pSPI->rx_dma_cfg, spi_get_index(pSPI->hw_inst)
? DREQ_SPI1_RX
: DREQ_SPI0_RX);
channel_config_set_read_increment(&pSPI->rx_dma_cfg, false);
/* Theory: we only need an interrupt on rx complete,
since if rx is complete, tx must also be complete. */
// Configure the processor to run dma_handler() when DMA IRQ 0/1 is
// asserted:
int irq = irqChannel1 ? DMA_IRQ_1 : DMA_IRQ_0;
if (irqShared) {
irq_add_shared_handler(
irq, pSPI->dma_isr,
PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
} else {
irq_set_exclusive_handler(irq, pSPI->dma_isr);
}
// Tell the DMA to raise IRQ line 0/1 when the channel finishes a block
if (irqChannel1) {
dma_channel_set_irq1_enabled(pSPI->rx_dma, true);
} else {
dma_channel_set_irq0_enabled(pSPI->rx_dma, true);
}
irq_set_enabled(irq, true);
LED_INIT();
pSPI->initialized = true;
spi_unlock(pSPI);
}
mutex_exit(&my_spi_init_mutex);
return true;
}
/* [] END OF FILE */