-
Notifications
You must be signed in to change notification settings - Fork 3k
SPI upgrade - per-peripheral mutex and GPIO-based SSEL #9469
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,31 +25,96 @@ | |
|
||
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; | ||
|
||
// 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() | ||
{ | ||
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(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) | ||
|
@@ -60,8 +125,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 +140,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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just a matter of coding style, but for clarity, could this increment be done after the if ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I did that quite deliberately because a couple of days before writing it I saw some existing code containing this:
which is not thread-safe, and needs to be
I think if people can't get comfortable with the increment/decrement pattern, it just encourages that sort of mistake. (Although breaking it up doesn't hurt in this case, as we are in a lock). |
||
_acquire(); | ||
_set_ssel(0); | ||
} | ||
} | ||
|
||
void SPI::unlock() | ||
{ | ||
_mutex->unlock(); | ||
_peripheral->mutex->unlock(); | ||
} | ||
|
||
void SPI::deselect() | ||
{ | ||
if (--_select_count == 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like in the previous commend, could this decrement be done before the if to avoid any confusion ? |
||
_set_ssel(1); | ||
} | ||
unlock(); | ||
} | ||
|
||
void SPI::set_default_write_value(char data) | ||
{ | ||
// this does not actually need to lock the peripheral. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So could we remove lock/unlock? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not necessarily - the lock is also functioning as the "API lock". If the SPI object is itself supposed to be thread-safe (as opposed to just thread safety for multiple SPI objects accessing one bus), then there needs to be synchronisation on its members. It currently seems that such objects are trying to be locally thread-safe, but maybe that needs more thought. |
||
lock(); | ||
_write_fill = data; | ||
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) | ||
{ | ||
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 +230,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 +241,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 +253,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 +273,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 +293,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 +325,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 +336,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); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the static array of peripheral management blocks contains
Mutex
es, and if those weren't wrapped in aSingletonPtr
, the array would be placed into RAM even if an image didn't use SPI.