Merge pull request #9469 from kjbracey-arm/spi_muxing

SPI upgrade - per-peripheral mutex and GPIO-based SSEL
pull/9067/head
Martin Kojtal 2019-03-01 18:38:27 +01:00 committed by GitHub
commit 7c9a71846e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 255 additions and 77 deletions

View File

@ -16,6 +16,7 @@
#ifndef MBED_SPIF_BLOCK_DEVICE_H #ifndef MBED_SPIF_BLOCK_DEVICE_H
#define MBED_SPIF_BLOCK_DEVICE_H #define MBED_SPIF_BLOCK_DEVICE_H
#include "platform/SingletonPtr.h"
#include "SPI.h" #include "SPI.h"
#include "DigitalOut.h" #include "DigitalOut.h"
#include "BlockDevice.h" #include "BlockDevice.h"

View File

@ -25,31 +25,96 @@
namespace mbed { namespace mbed {
#if DEVICE_SPI_ASYNCH && TRANSACTION_QUEUE_SIZE_SPI SPI::spi_peripheral_s SPI::_peripherals[SPI_PERIPHERALS_USED];
CircularBuffer<Transaction<SPI>, TRANSACTION_QUEUE_SIZE_SPI> SPI::_transaction_buffer; int SPI::_peripherals_used;
#endif
SPI::SPI(PinName mosi, PinName miso, PinName sclk, PinName ssel) : SPI::SPI(PinName mosi, PinName miso, PinName sclk, PinName ssel) :
_spi(),
#if DEVICE_SPI_ASYNCH #if DEVICE_SPI_ASYNCH
_irq(this), _irq(this),
_usage(DMA_USAGE_NEVER),
_deep_sleep_locked(false),
#endif #endif
_bits(8), _mosi(mosi),
_mode(0), _miso(miso),
_hz(1000000), _sclk(sclk),
_write_fill(SPI_FILL_CHAR) _hw_ssel(ssel),
_sw_ssel(NC)
{
_do_construct();
}
SPI::SPI(PinName mosi, PinName miso, PinName sclk, PinName ssel, use_gpio_ssel_t) :
#if DEVICE_SPI_ASYNCH
_irq(this),
#endif
_mosi(mosi),
_miso(miso),
_sclk(sclk),
_hw_ssel(NC),
_sw_ssel(ssel, 1)
{
_do_construct();
}
void SPI::_do_construct()
{ {
// No lock needed in the constructor // No lock needed in the constructor
spi_init(&_spi, mosi, miso, sclk, ssel); #if DEVICE_SPI_ASYNCH
_usage = DMA_USAGE_NEVER;
_deep_sleep_locked = false;
#endif
_select_count = 0;
_bits = 8;
_mode = 0;
_hz = 1000000;
_write_fill = SPI_FILL_CHAR;
// Need backwards compatibility with HALs not providing API
#ifdef DEVICE_SPI_COUNT
SPIName name = spi_get_peripheral_name(_mosi, _miso, _sclk);
#else
SPIName name = GlobalSPI;
#endif
core_util_critical_section_enter();
// lookup in a critical section if we already have it else initialize it
_peripheral = SPI::_lookup(name);
if (!_peripheral) {
_peripheral = SPI::_alloc();
_peripheral->name = name;
}
core_util_critical_section_exit();
// we don't need to _acquire at this stage.
// this will be done anyway before any operation.
} }
SPI::~SPI() SPI::~SPI()
{ {
if (_owner == this) { lock();
_owner = NULL; /* Make sure a stale pointer isn't left in peripheral's owner field */
if (_peripheral->owner == this) {
_peripheral->owner = NULL;
} }
unlock();
}
SPI::spi_peripheral_s *SPI::_lookup(SPI::SPIName name)
{
SPI::spi_peripheral_s *result = NULL;
core_util_critical_section_enter();
for (int idx = 0; idx < _peripherals_used; idx++) {
if (_peripherals[idx].name == name) {
result = &_peripherals[idx];
break;
}
}
core_util_critical_section_exit();
return result;
}
SPI::spi_peripheral_s *SPI::_alloc()
{
MBED_ASSERT(_peripherals_used < SPI_PERIPHERALS_USED);
return &_peripherals[_peripherals_used++];
} }
void SPI::format(int bits, int mode) void SPI::format(int bits, int mode)
@ -60,8 +125,8 @@ void SPI::format(int bits, int mode)
// If changing format while you are the owner then just // If changing format while you are the owner then just
// update format, but if owner is changed then even frequency should be // update format, but if owner is changed then even frequency should be
// updated which is done by acquire. // updated which is done by acquire.
if (_owner == this) { if (_peripheral->owner == this) {
spi_format(&_spi, _bits, _mode, 0); spi_format(&_peripheral->spi, _bits, _mode, 0);
} else { } else {
_acquire(); _acquire();
} }
@ -75,69 +140,78 @@ void SPI::frequency(int hz)
// If changing format while you are the owner then just // If changing format while you are the owner then just
// update frequency, but if owner is changed then even frequency should be // update frequency, but if owner is changed then even frequency should be
// updated which is done by acquire. // updated which is done by acquire.
if (_owner == this) { if (_peripheral->owner == this) {
spi_frequency(&_spi, _hz); spi_frequency(&_peripheral->spi, _hz);
} else { } else {
_acquire(); _acquire();
} }
unlock(); unlock();
} }
SPI *SPI::_owner = NULL;
SingletonPtr<PlatformMutex> SPI::_mutex;
// ignore the fact there are multiple physical spis, and always update if it wasn't us last
void SPI::aquire()
{
lock();
if (_owner != this) {
spi_format(&_spi, _bits, _mode, 0);
spi_frequency(&_spi, _hz);
_owner = this;
}
unlock();
}
// Note: Private function with no locking // Note: Private function with no locking
void SPI::_acquire() void SPI::_acquire()
{ {
if (_owner != this) { if (_peripheral->owner != this) {
spi_format(&_spi, _bits, _mode, 0); spi_init(&_peripheral->spi, _mosi, _miso, _sclk, _hw_ssel);
spi_frequency(&_spi, _hz); spi_format(&_peripheral->spi, _bits, _mode, 0);
_owner = this; spi_frequency(&_peripheral->spi, _hz);
_peripheral->owner = this;
} }
} }
int SPI::write(int value) int SPI::write(int value)
{ {
lock(); select();
_acquire(); int ret = spi_master_write(&_peripheral->spi, value);
int ret = spi_master_write(&_spi, value); deselect();
unlock();
return ret; return ret;
} }
int SPI::write(const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length) int SPI::write(const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length)
{ {
lock(); select();
_acquire(); int ret = spi_master_block_write(&_peripheral->spi, tx_buffer, tx_length, rx_buffer, rx_length, _write_fill);
int ret = spi_master_block_write(&_spi, tx_buffer, tx_length, rx_buffer, rx_length, _write_fill); deselect();
unlock();
return ret; return ret;
} }
void SPI::_set_ssel(int val)
{
if (_sw_ssel.is_connected()) {
_sw_ssel = val;
}
}
void SPI::lock() void SPI::lock()
{ {
_mutex->lock(); _peripheral->mutex->lock();
}
void SPI::select()
{
lock();
if (_select_count++ == 0) {
_acquire();
_set_ssel(0);
}
} }
void SPI::unlock() void SPI::unlock()
{ {
_mutex->unlock(); _peripheral->mutex->unlock();
}
void SPI::deselect()
{
if (--_select_count == 0) {
_set_ssel(1);
}
unlock();
} }
void SPI::set_default_write_value(char data) void SPI::set_default_write_value(char data)
{ {
// this does not actually need to lock the peripheral.
lock(); lock();
_write_fill = data; _write_fill = data;
unlock(); unlock();
@ -147,7 +221,7 @@ void SPI::set_default_write_value(char data)
int SPI::transfer(const void *tx_buffer, int tx_length, void *rx_buffer, int rx_length, unsigned char bit_width, const event_callback_t &callback, int event) int SPI::transfer(const void *tx_buffer, int tx_length, void *rx_buffer, int rx_length, unsigned char bit_width, const event_callback_t &callback, int event)
{ {
if (spi_active(&_spi)) { if (spi_active(&_peripheral->spi)) {
return queue_transfer(tx_buffer, tx_length, rx_buffer, rx_length, bit_width, callback, event); return queue_transfer(tx_buffer, tx_length, rx_buffer, rx_length, bit_width, callback, event);
} }
start_transfer(tx_buffer, tx_length, rx_buffer, rx_length, bit_width, callback, event); start_transfer(tx_buffer, tx_length, rx_buffer, rx_length, bit_width, callback, event);
@ -156,7 +230,7 @@ int SPI::transfer(const void *tx_buffer, int tx_length, void *rx_buffer, int rx_
void SPI::abort_transfer() void SPI::abort_transfer()
{ {
spi_abort_asynch(&_spi); spi_abort_asynch(&_peripheral->spi);
unlock_deep_sleep(); unlock_deep_sleep();
#if TRANSACTION_QUEUE_SIZE_SPI #if TRANSACTION_QUEUE_SIZE_SPI
dequeue_transaction(); dequeue_transaction();
@ -167,7 +241,7 @@ void SPI::abort_transfer()
void SPI::clear_transfer_buffer() void SPI::clear_transfer_buffer()
{ {
#if TRANSACTION_QUEUE_SIZE_SPI #if TRANSACTION_QUEUE_SIZE_SPI
_transaction_buffer.reset(); _peripheral->transaction_buffer->reset();
#endif #endif
} }
@ -179,7 +253,7 @@ void SPI::abort_all_transfers()
int SPI::set_dma_usage(DMAUsage usage) int SPI::set_dma_usage(DMAUsage usage)
{ {
if (spi_active(&_spi)) { if (spi_active(&_peripheral->spi)) {
return -1; return -1;
} }
_usage = usage; _usage = usage;
@ -199,12 +273,12 @@ int SPI::queue_transfer(const void *tx_buffer, int tx_length, void *rx_buffer, i
t.callback = callback; t.callback = callback;
t.width = bit_width; t.width = bit_width;
Transaction<SPI> transaction(this, t); Transaction<SPI> transaction(this, t);
if (_transaction_buffer.full()) { if (_peripheral->transaction_buffer->full()) {
return -1; // the buffer is full return -1; // the buffer is full
} else { } else {
core_util_critical_section_enter(); core_util_critical_section_enter();
_transaction_buffer.push(transaction); _peripheral->transaction_buffer->push(transaction);
if (!spi_active(&_spi)) { if (!spi_active(&_peripheral->spi)) {
dequeue_transaction(); dequeue_transaction();
} }
core_util_critical_section_exit(); core_util_critical_section_exit();
@ -219,9 +293,10 @@ void SPI::start_transfer(const void *tx_buffer, int tx_length, void *rx_buffer,
{ {
lock_deep_sleep(); lock_deep_sleep();
_acquire(); _acquire();
_set_ssel(0);
_callback = callback; _callback = callback;
_irq.callback(&SPI::irq_handler_asynch); _irq.callback(&SPI::irq_handler_asynch);
spi_master_transfer(&_spi, tx_buffer, tx_length, rx_buffer, rx_length, bit_width, _irq.entry(), event, _usage); spi_master_transfer(&_peripheral->spi, tx_buffer, tx_length, rx_buffer, rx_length, bit_width, _irq.entry(), event, _usage);
} }
void SPI::lock_deep_sleep() void SPI::lock_deep_sleep()
@ -250,7 +325,7 @@ void SPI::start_transaction(transaction_t *data)
void SPI::dequeue_transaction() void SPI::dequeue_transaction()
{ {
Transaction<SPI> t; Transaction<SPI> t;
if (_transaction_buffer.pop(t)) { if (_peripheral->transaction_buffer->pop(t)) {
SPI *obj = t.get_object(); SPI *obj = t.get_object();
transaction_t *data = t.get_transaction(); transaction_t *data = t.get_transaction();
obj->start_transaction(data); obj->start_transaction(data);
@ -261,8 +336,9 @@ void SPI::dequeue_transaction()
void SPI::irq_handler_asynch(void) void SPI::irq_handler_asynch(void)
{ {
int event = spi_irq_handler_asynch(&_spi); int event = spi_irq_handler_asynch(&_peripheral->spi);
if (_callback && (event & SPI_EVENT_ALL)) { if (_callback && (event & SPI_EVENT_ALL)) {
_set_ssel(1);
unlock_deep_sleep(); unlock_deep_sleep();
_callback.call(event & SPI_EVENT_ALL); _callback.call(event & SPI_EVENT_ALL);
} }

View File

@ -23,9 +23,19 @@
#include "platform/PlatformMutex.h" #include "platform/PlatformMutex.h"
#include "hal/spi_api.h" #include "hal/spi_api.h"
#include "drivers/DigitalOut.h"
#include "platform/SingletonPtr.h" #include "platform/SingletonPtr.h"
#include "platform/NonCopyable.h" #include "platform/NonCopyable.h"
#if defined MBED_CONF_DRIVERS_SPI_COUNT_MAX && DEVICE_SPI_COUNT > MBED_CONF_DRIVERS_SPI_COUNT_MAX
#define SPI_PERIPHERALS_USED MBED_CONF_DRIVERS_SPI_COUNT_MAX
#elif defined DEVICE_SPI_COUNT
#define SPI_PERIPHERALS_USED DEVICE_SPI_COUNT
#else
/* Backwards compatibility with HALs not providing DEVICE_SPI_COUNT */
#define SPI_PERIPHERALS_USED 1
#endif
#if DEVICE_SPI_ASYNCH #if DEVICE_SPI_ASYNCH
#include "platform/CThunk.h" #include "platform/CThunk.h"
#include "hal/dma_api.h" #include "hal/dma_api.h"
@ -37,6 +47,9 @@
namespace mbed { namespace mbed {
/** \addtogroup drivers */ /** \addtogroup drivers */
struct use_gpio_ssel_t { };
const use_gpio_ssel_t use_gpio_ssel;
/** A SPI Master, used for communicating with SPI slave devices. /** A SPI Master, used for communicating with SPI slave devices.
* *
* The default format is set to 8-bits, mode 0, and a clock frequency of 1MHz. * The default format is set to 8-bits, mode 0, and a clock frequency of 1MHz.
@ -84,6 +97,11 @@ class SPI : private NonCopyable<SPI> {
public: public:
/** Create a SPI master connected to the specified pins. /** Create a SPI master connected to the specified pins.
*
* @note This constructor passes the SSEL pin selection to the target HAL.
* Not all targets support SSEL, so this cannot be relied on in portable code.
* Portable code should use the alternative constructor that uses GPIO
* for SSEL.
* *
* @note You can specify mosi or miso as NC if not used. * @note You can specify mosi or miso as NC if not used.
* *
@ -93,6 +111,23 @@ public:
* @param ssel SPI Chip Select pin. * @param ssel SPI Chip Select pin.
*/ */
SPI(PinName mosi, PinName miso, PinName sclk, PinName ssel = NC); SPI(PinName mosi, PinName miso, PinName sclk, PinName ssel = NC);
/** Create a SPI master connected to the specified pins.
*
* @note This constructor manipulates the SSEL pin as a GPIO output
* using a DigitalOut object. This should work on any target, and permits
* the use of select() and deselect() methods to keep the pin asserted
* between transfers.
*
* @note You can specify mosi or miso as NC if not used.
*
* @param mosi SPI Master Out, Slave In pin.
* @param miso SPI Master In, Slave Out pin.
* @param sclk SPI Clock pin.
* @param ssel SPI Chip Select pin.
*/
SPI(PinName mosi, PinName miso, PinName sclk, PinName ssel, use_gpio_ssel_t);
virtual ~SPI(); virtual ~SPI();
/** Configure the data transmission format. /** Configure the data transmission format.
@ -149,6 +184,17 @@ public:
*/ */
virtual void unlock(void); virtual void unlock(void);
/** Assert the Slave Select line, acquiring exclusive access to this SPI bus.
*
* If use_gpio_ssel was not passed to the constructor, this only acquires
* exclusive access; it cannot assert the Slave Select line.
*/
void select(void);
/** Deassert the Slave Select line, releasing exclusive access to this SPI bus.
*/
void deselect(void);
/** Set default write data. /** Set default write data.
* SPI requires the master to send some data during a read operation. * SPI requires the master to send some data during a read operation.
* Different devices may require different default byte values. * Different devices may require different default byte values.
@ -180,7 +226,7 @@ public:
template<typename Type> template<typename Type>
int transfer(const Type *tx_buffer, int tx_length, Type *rx_buffer, int rx_length, const event_callback_t &callback, int event = SPI_EVENT_COMPLETE) int transfer(const Type *tx_buffer, int tx_length, Type *rx_buffer, int rx_length, const event_callback_t &callback, int event = SPI_EVENT_COMPLETE)
{ {
if (spi_active(&_spi)) { if (spi_active(&_peripheral->spi)) {
return queue_transfer(tx_buffer, tx_length, rx_buffer, rx_length, sizeof(Type) * 8, callback, event); return queue_transfer(tx_buffer, tx_length, rx_buffer, rx_length, sizeof(Type) * 8, callback, event);
} }
start_transfer(tx_buffer, tx_length, rx_buffer, rx_length, sizeof(Type) * 8, callback, event); start_transfer(tx_buffer, tx_length, rx_buffer, rx_length, sizeof(Type) * 8, callback, event);
@ -211,6 +257,7 @@ public:
#if !defined(DOXYGEN_ONLY) #if !defined(DOXYGEN_ONLY)
protected: protected:
/** SPI interrupt handler. /** SPI interrupt handler.
*/ */
void irq_handler_asynch(void); void irq_handler_asynch(void);
@ -274,7 +321,6 @@ private:
#if TRANSACTION_QUEUE_SIZE_SPI #if TRANSACTION_QUEUE_SIZE_SPI
/** Start a new transaction. /** Start a new transaction.
* *
* @param data Transaction data. * @param data Transaction data.
@ -285,18 +331,43 @@ private:
*/ */
void dequeue_transaction(); void dequeue_transaction();
/* Queue of pending transfers */ #endif // TRANSACTION_QUEUE_SIZE_SPI
static CircularBuffer<Transaction<SPI>, TRANSACTION_QUEUE_SIZE_SPI> _transaction_buffer;
#endif
#endif // !defined(DOXYGEN_ONLY) #endif // !defined(DOXYGEN_ONLY)
#endif // DEVICE_SPI_ASYNCH #endif // DEVICE_SPI_ASYNCH
#if !defined(DOXYGEN_ONLY) #if !defined(DOXYGEN_ONLY)
protected: protected:
/* Internal SPI object identifying the resources */ #ifdef DEVICE_SPI_COUNT
spi_t _spi; // HAL must have defined this as a global enum
typedef ::SPIName SPIName;
#else
// HAL may or may not have defined it - use a local definition
enum SPIName { GlobalSPI };
#endif
struct spi_peripheral_s {
/* Internal SPI name identifying the resources. */
SPIName name;
/* Internal SPI object handling the resources' state. */
spi_t spi;
/* Used by lock and unlock for thread safety */
SingletonPtr<PlatformMutex> mutex;
/* Current user of the SPI */
SPI *owner;
#if DEVICE_SPI_ASYNCH && TRANSACTION_QUEUE_SIZE_SPI
/* Queue of pending transfers */
SingletonPtr<CircularBuffer<Transaction<SPI>, TRANSACTION_QUEUE_SIZE_SPI> > transaction_buffer;
#endif
};
// holds spi_peripheral_s per peripheral on the device.
// Drawback: it costs ram size even if the device is not used, however
// application can limit the allocation via JSON.
static spi_peripheral_s _peripherals[SPI_PERIPHERALS_USED];
static int _peripherals_used;
// Holds the reference to the associated peripheral.
spi_peripheral_s *_peripheral;
#if DEVICE_SPI_ASYNCH #if DEVICE_SPI_ASYNCH
/* Interrupt */ /* Interrupt */
@ -307,14 +378,17 @@ protected:
DMAUsage _usage; DMAUsage _usage;
/* Current sate of the sleep manager */ /* Current sate of the sleep manager */
bool _deep_sleep_locked; bool _deep_sleep_locked;
#endif #endif // DEVICE_SPI_ASYNCH
// Configuration.
PinName _mosi;
PinName _miso;
PinName _sclk;
PinName _hw_ssel;
// The Slave Select GPIO if we're doing it ourselves.
DigitalOut _sw_ssel;
/* Take over the physical SPI and apply our settings (thread safe) */
void aquire(void);
/* Current user of the SPI */
static SPI *_owner;
/* Used by lock and unlock for thread safety */
static SingletonPtr<PlatformMutex> _mutex;
/* Size of the SPI frame */ /* Size of the SPI frame */
int _bits; int _bits;
/* Clock polairy and phase */ /* Clock polairy and phase */
@ -323,18 +397,30 @@ protected:
int _hz; int _hz;
/* Default character used for NULL transfers */ /* Default character used for NULL transfers */
char _write_fill; char _write_fill;
/* Select count to handle re-entrant selection */
int8_t _select_count;
private: private:
void _do_construct();
/** Private acquire function without locking/unlocking. /** Private acquire function without locking/unlocking.
* Implemented in order to avoid duplicate locking and boost performance. * Implemented in order to avoid duplicate locking and boost performance.
*/ */
void _acquire(void); void _acquire(void);
void _set_ssel(int);
/** Private lookup in the static _peripherals table.
*/
static spi_peripheral_s *_lookup(SPIName name);
/** Allocate an entry in the static _peripherals table.
*/
static spi_peripheral_s *_alloc();
#endif //!defined(DOXYGEN_ONLY) #endif //!defined(DOXYGEN_ONLY)
}; };
} // namespace mbed } // namespace mbed
#endif #endif // DEVICE_SPI || DOXYGEN_ONLY
#endif #endif // MBED_SPI_H

View File

@ -8,6 +8,10 @@
"uart-serial-rxbuf-size": { "uart-serial-rxbuf-size": {
"help": "Default RX buffer size for a UARTSerial instance (unit Bytes))", "help": "Default RX buffer size for a UARTSerial instance (unit Bytes))",
"value": 256 "value": 256
},
"spi_count_max": {
"help": "The maximum number of SPI peripherals used at the same time. Determines RAM allocated for SPI peripheral management. If null, limit determined by hardware.",
"value": null
} }
} }
} }

View File

@ -62,6 +62,17 @@ extern "C" {
* @{ * @{
*/ */
#ifdef DEVICE_SPI_COUNT
/**
* Returns a variant of the SPIName enum uniquely identifying a SPI peripheral of the device.
* @param[in] mosi The pin to use for MOSI
* @param[in] miso The pin to use for MISO
* @param[in] sclk The pin to use for SCLK
* @return An SPI peripheral identifier
*/
SPIName spi_get_peripheral_name(PinName mosi, PinName miso, PinName mclk);
#endif
/** Initialize the SPI peripheral /** Initialize the SPI peripheral
* *
* Configures the pins used by SPI, sets a default format and frequency, and enables the peripheral * Configures the pins used by SPI, sets a default format and frequency, and enables the peripheral

View File

@ -57,7 +57,7 @@ typedef enum {
UART_8 = (int)UART8_BASE UART_8 = (int)UART8_BASE
} UARTName; } UARTName;
#define SPI_COUNT 6 #define DEVICE_SPI_COUNT 6
typedef enum { typedef enum {
SPI_1 = (int)SPI1_BASE, SPI_1 = (int)SPI1_BASE,
SPI_2 = (int)SPI2_BASE, SPI_2 = (int)SPI2_BASE,