diff --git a/drivers/SPI.cpp b/drivers/SPI.cpp index 8ae1ee2f4e..daf2563d64 100644 --- a/drivers/SPI.cpp +++ b/drivers/SPI.cpp @@ -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_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 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 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 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); } diff --git a/drivers/SPI.h b/drivers/SPI.h index 65db21bbb3..592c55bec6 100644 --- a/drivers/SPI.h +++ b/drivers/SPI.h @@ -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 { 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 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_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 mutex; + /* Current user of the SPI */ + SPI *owner; +#if DEVICE_SPI_ASYNCH && TRANSACTION_QUEUE_SIZE_SPI + /* Queue of pending transfers */ + SingletonPtr, 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 _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 diff --git a/drivers/mbed_lib.json b/drivers/mbed_lib.json index c268b6381a..4043c309db 100644 --- a/drivers/mbed_lib.json +++ b/drivers/mbed_lib.json @@ -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 } } } diff --git a/hal/spi_api.h b/hal/spi_api.h index 5811f71d7b..ad80cf466e 100644 --- a/hal/spi_api.h +++ b/hal/spi_api.h @@ -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