SPI upgrade - per-peripheral mutex and GPIO-based SSEL

This commit takes some of the work done on the SPI class from #8445, and
refines it, to provide the per-peripheral mutex functionality.

This also implements GPIO-based SSEL, which exposes a new
select()/deselect() API for users to group transfers, and should work on
every platform (unlike the HAL-based SSEL). This requires users to use a
new constructor to avoid backwards compatibility issues.

To activate the per-peripheral mutex, the HAL must define SPI_COUNT and
provide spi_get_peripheral_name().  (In #8445 this is a reworked
spi_get_module, but the name is changed here to avoid a collision with
existing HALs - this commit is designed to work without wider HAL
changes).

Fixes: #9149
pull/9469/head
Kevin Bracey 2019-01-23 14:14:30 +02:00
parent 857cd9fba1
commit 5e059b7d1d
4 changed files with 245 additions and 76 deletions

View File

@ -23,33 +23,99 @@
#if DEVICE_SPI
/* Backwards compatibility with HALs that don't provide this */
MBED_WEAK SPIName spi_get_peripheral_name(PinName /*mosi*/, PinName /*miso*/, PinName /*mclk*/)
{
return (SPIName)1;
}
namespace mbed {
#if DEVICE_SPI_ASYNCH && TRANSACTION_QUEUE_SIZE_SPI
CircularBuffer<Transaction<SPI>, TRANSACTION_QUEUE_SIZE_SPI> SPI::_transaction_buffer;
#endif
SPI::spi_peripheral_s SPI::_peripherals[SPI_PERIPHERALS_USED];
int SPI::_peripherals_used;
SPI::SPI(PinName mosi, PinName miso, PinName sclk, PinName ssel) :
_spi(),
#if DEVICE_SPI_ASYNCH
_irq(this),
_usage(DMA_USAGE_NEVER),
_deep_sleep_locked(false),
#endif
_bits(8),
_mode(0),
_hz(1000000),
_write_fill(SPI_FILL_CHAR)
_mosi(mosi),
_miso(miso),
_sclk(sclk),
_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
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;
SPIName name = spi_get_peripheral_name(_mosi, _miso, _sclk);
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()
{
if (_owner == this) {
_owner = NULL;
lock();
/* 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(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)
@ -60,8 +126,8 @@ void SPI::format(int bits, int mode)
// If changing format while you are the owner then just
// update format, but if owner is changed then even frequency should be
// updated which is done by acquire.
if (_owner == this) {
spi_format(&_spi, _bits, _mode, 0);
if (_peripheral->owner == this) {
spi_format(&_peripheral->spi, _bits, _mode, 0);
} else {
_acquire();
}
@ -75,69 +141,78 @@ void SPI::frequency(int hz)
// If changing format while you are the owner then just
// update frequency, but if owner is changed then even frequency should be
// updated which is done by acquire.
if (_owner == this) {
spi_frequency(&_spi, _hz);
if (_peripheral->owner == this) {
spi_frequency(&_peripheral->spi, _hz);
} else {
_acquire();
}
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
void SPI::_acquire()
{
if (_owner != this) {
spi_format(&_spi, _bits, _mode, 0);
spi_frequency(&_spi, _hz);
_owner = this;
if (_peripheral->owner != this) {
spi_init(&_peripheral->spi, _mosi, _miso, _sclk, _hw_ssel);
spi_format(&_peripheral->spi, _bits, _mode, 0);
spi_frequency(&_peripheral->spi, _hz);
_peripheral->owner = this;
}
}
int SPI::write(int value)
{
lock();
_acquire();
int ret = spi_master_write(&_spi, value);
unlock();
select();
int ret = spi_master_write(&_peripheral->spi, value);
deselect();
return ret;
}
int SPI::write(const char *tx_buffer, int tx_length, char *rx_buffer, int rx_length)
{
lock();
_acquire();
int ret = spi_master_block_write(&_spi, tx_buffer, tx_length, rx_buffer, rx_length, _write_fill);
unlock();
select();
int ret = spi_master_block_write(&_peripheral->spi, tx_buffer, tx_length, rx_buffer, rx_length, _write_fill);
deselect();
return ret;
}
void SPI::_set_ssel(int val)
{
if (_sw_ssel.is_connected()) {
_sw_ssel = val;
}
}
void SPI::lock()
{
_mutex->lock();
_peripheral->mutex->lock();
}
void SPI::select()
{
lock();
if (_select_count++ == 0) {
_acquire();
_set_ssel(0);
}
}
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)
{
// this does not actually need to lock the peripheral.
lock();
_write_fill = data;
unlock();
@ -147,7 +222,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)
{
if (spi_active(&_spi)) {
if (spi_active(&_peripheral->spi)) {
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);
@ -156,7 +231,7 @@ int SPI::transfer(const void *tx_buffer, int tx_length, void *rx_buffer, int rx_
void SPI::abort_transfer()
{
spi_abort_asynch(&_spi);
spi_abort_asynch(&_peripheral->spi);
unlock_deep_sleep();
#if TRANSACTION_QUEUE_SIZE_SPI
dequeue_transaction();
@ -167,7 +242,7 @@ void SPI::abort_transfer()
void SPI::clear_transfer_buffer()
{
#if TRANSACTION_QUEUE_SIZE_SPI
_transaction_buffer.reset();
_peripheral->transaction_buffer->reset();
#endif
}
@ -179,7 +254,7 @@ void SPI::abort_all_transfers()
int SPI::set_dma_usage(DMAUsage usage)
{
if (spi_active(&_spi)) {
if (spi_active(&_peripheral->spi)) {
return -1;
}
_usage = usage;
@ -199,12 +274,12 @@ int SPI::queue_transfer(const void *tx_buffer, int tx_length, void *rx_buffer, i
t.callback = callback;
t.width = bit_width;
Transaction<SPI> transaction(this, t);
if (_transaction_buffer.full()) {
if (_peripheral->transaction_buffer->full()) {
return -1; // the buffer is full
} else {
core_util_critical_section_enter();
_transaction_buffer.push(transaction);
if (!spi_active(&_spi)) {
_peripheral->transaction_buffer->push(transaction);
if (!spi_active(&_peripheral->spi)) {
dequeue_transaction();
}
core_util_critical_section_exit();
@ -219,9 +294,10 @@ void SPI::start_transfer(const void *tx_buffer, int tx_length, void *rx_buffer,
{
lock_deep_sleep();
_acquire();
_set_ssel(0);
_callback = callback;
_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()
@ -250,7 +326,7 @@ void SPI::start_transaction(transaction_t *data)
void SPI::dequeue_transaction()
{
Transaction<SPI> t;
if (_transaction_buffer.pop(t)) {
if (_peripheral->transaction_buffer->pop(t)) {
SPI *obj = t.get_object();
transaction_t *data = t.get_transaction();
obj->start_transaction(data);
@ -261,8 +337,9 @@ void SPI::dequeue_transaction()
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)) {
_set_ssel(1);
unlock_deep_sleep();
_callback.call(event & SPI_EVENT_ALL);
}

View File

@ -23,9 +23,21 @@
#include "platform/PlatformMutex.h"
#include "hal/spi_api.h"
#include "drivers/DigitalOut.h"
#include "platform/SingletonPtr.h"
#include "platform/NonCopyable.h"
/* Backwards compatibility with HALs not providing this */
#ifndef SPI_COUNT
#define SPI_COUNT 1
#endif
#if defined MBED_CONF_DRIVERS_SPI_COUNT_MAX && SPI_COUNT > MBED_CONF_DRIVERS_SPI_COUNT_MAX
#define SPI_PERIPHERALS_USED MBED_CONF_DRIVERS_SPI_COUNT_MAX
#else
#define SPI_PERIPHERALS_USED SPI_COUNT
#endif
#if DEVICE_SPI_ASYNCH
#include "platform/CThunk.h"
#include "hal/dma_api.h"
@ -37,6 +49,9 @@
namespace mbed {
/** \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.
*
* The default format is set to 8-bits, mode 0, and a clock frequency of 1MHz.
@ -84,6 +99,11 @@ class SPI : private NonCopyable<SPI> {
public:
/** 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.
*
@ -93,6 +113,23 @@ public:
* @param ssel SPI Chip Select pin.
*/
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();
/** Configure the data transmission format.
@ -149,6 +186,17 @@ public:
*/
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.
* SPI requires the master to send some data during a read operation.
* Different devices may require different default byte values.
@ -180,7 +228,7 @@ public:
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)
{
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);
}
start_transfer(tx_buffer, tx_length, rx_buffer, rx_length, sizeof(Type) * 8, callback, event);
@ -274,7 +322,6 @@ private:
#if TRANSACTION_QUEUE_SIZE_SPI
/** Start a new transaction.
*
* @param data Transaction data.
@ -285,18 +332,35 @@ private:
*/
void dequeue_transaction();
/* Queue of pending transfers */
static CircularBuffer<Transaction<SPI>, TRANSACTION_QUEUE_SIZE_SPI> _transaction_buffer;
#endif
#endif //!defined(DOXYGEN_ONLY)
#endif //DEVICE_SPI_ASYNCH
#endif // TRANSACTION_QUEUE_SIZE_SPI
#endif // !defined(DOXYGEN_ONLY)
#endif // DEVICE_SPI_ASYNCH
#if !defined(DOXYGEN_ONLY)
protected:
/* Internal SPI object identifying the resources */
spi_t _spi;
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
/* Interrupt */
@ -307,14 +371,17 @@ protected:
DMAUsage _usage;
/* Current sate of the sleep manager */
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 */
int _bits;
/* Clock polairy and phase */
@ -323,18 +390,30 @@ protected:
int _hz;
/* Default character used for NULL transfers */
char _write_fill;
/* Select count to handle re-entrant selection */
int8_t _select_count;
private:
void _do_construct();
/** Private acquire function without locking/unlocking.
* Implemented in order to avoid duplicate locking and boost performance.
*/
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)
};
} // namespace mbed
#endif
#endif // DEVICE_SPI || DOXYGEN_ONLY
#endif
#endif // MBED_SPI_H

View File

@ -8,6 +8,10 @@
"uart-serial-rxbuf-size": {
"help": "Default RX buffer size for a UARTSerial instance (unit Bytes))",
"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,15 @@ extern "C" {
* @{
*/
/**
* 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);
/** Initialize the SPI peripheral
*
* Configures the pins used by SPI, sets a default format and frequency, and enables the peripheral