From 6cb9dd86bc553516a40c4fef7759b4ad18ff3fb1 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Fri, 6 Jul 2018 17:40:58 +0100 Subject: [PATCH] add spi-overhaul rfc document --- .../design-documents/hal/0000-spi-overhaul.md | 269 ++++++++++++++++++ docs/design-documents/hal/spi_irq_flow.png | Bin 0 -> 15419 bytes 2 files changed, 269 insertions(+) create mode 100644 docs/design-documents/hal/0000-spi-overhaul.md create mode 100644 docs/design-documents/hal/spi_irq_flow.png diff --git a/docs/design-documents/hal/0000-spi-overhaul.md b/docs/design-documents/hal/0000-spi-overhaul.md new file mode 100644 index 00000000000..d296d0d844a --- /dev/null +++ b/docs/design-documents/hal/0000-spi-overhaul.md @@ -0,0 +1,269 @@ +# HAL overhaul : SPI + +## Description + +This update targets the SPI HAL as the first step of a wide plan for all HAL APIs. + +## Motivation + +This work is part of the effort to bring well defined specification to APIs in `mbed-os`. Some users have shown interest in some addition to the supported features of this API ([bit ordering selection #4789](https://github.com/ARMmbed/mbed-os/issues/4789)). +Analysis of previous work also revealed some inconsistencies in the current SPI HAL API that this RFC aims at fixing. Those inconsistencies are listed in the next chapter. +Finally, `mbed-os` has grown a lot its supported target count and some of the oldest one could benefit quite significantly in maintenance cost from a factorisation of their code base. + +### Inconsistencies + +- The type used in block transfer functions vary between `(const) void*` and `(const) char*`. +- Some method have a parameter to set the width of the symbols while it is part of the `spi_format` arguments. +- Some method provide a `fill_symbol` parameter as a `char` which would not allow to set a symbol whose width is over 8bits. +- Some method that allow to read more symbol than they write miss an argument to set the `fill_symbol`. +- `spi_master_transfer` takes a `uint32_t` instead of a function pointer type for its `handler` argument. +- Some method have misleading naming (e.g. `spi_slave_receive` and `spi_slave_read`). +- The slave and master API are "almost" identical. There is no `spi_mater_read` nor `spi_master_receive`. +- `spi_format` should use an enum for the mode. + +## Use-cases + +In order to provide a meaningful API, this RFC will consider the following use cases. + +### Use-cases in Mbed-OS +(click for detailed description) +-
SPI Master: sd-driver
+-
SPI Master: dataflash-driver
+-
SPI Master: C12832 LCD screen driver and ADXL345 3 axis accelerometer
+-
SPI Master: ism43362 (WiFi module)
+-
SPI Master: atmel-rf (802.15.4)
+-
SPI Master: stm-spirit1-rf (Sub-1 GHz RF based on the SPSGRF-868 Module)
+-
SPI Master: ble-x-nucleo-idb0xa1 (BLE module)
+-
SPI Master: ADC
+-
SPI Master: AppNearMe MuNFC PN532
+ +### Other use cases +- SPI Slave + In this use case the user wants to receive a command frame and send back an answer frame. + Both frames are constructed that way : + + | size | frame type | variable length frame | crc + | --- | --- | --- | --- + | 2 bytes | 1 byte | \ bytes | 1 byte + + Data are sent as 8 bit symbols, little endian, lsb first. The Frame cannot exceed 2048bytes in length. + CS has to stay asserted between request and response. + The master expect to receive the symbol `0xFF` as a place holder. +- Simultaneous transaction over multiple SPI peripheral. + For example: Reading/Writing to an SD Card while using the communication interface over another SPI interface (other peripheral). +- Asynchronous: + These are basically the same the regular ones except that the function call should return immediately and a callback should be triggered once the operation is completed. + - SPI Master block transfer + - SPI Slave block transfer : Not supported in current API + + The asynchronous API is particularly important when dealing with multiple interfaces. Indeed, handling simultaneously two SPI transfers would require two thread with a blocking API while only one is required by the asynchronous API saving kilobytes of RAM. +- Half-duplex mode: + The [LPS22HB](https://www.st.com/en/mems-and-sensors/lps22hb.html) product family can be controlled through a half-duplex SPI interface (called 3 wire mode in the data sheet). + +## API Changes + +| Old | New | Motivation +| --- | --- | --- +| `spi_format(spi, bits, mode, slave)` | `spi_format(spi, bits, mode, slave, bit_ordering)` | Add the bit ordering as requested by [#4789](https://github.com/ARMmbed/mbed-os/issues/4789) +| `spi_format(..., int bits, ...)` | `spi_format(..., uint8_t bits, ...)` | `bits` becomes an unsigned 8 bits integer as the value is in the range \[1; 32]. +| `spi_format(..., int mode, ...)` | `spi_format(..., spi_mode_t mode, ...)` | SPI mode becomes an enum to sanitize the "magic integer" +| `spi_format(..., int slave, ...)` | `spi_init(..., bool is_slave, ...)` | `slave` becomes a boolean called `is_slave` as it is only a binary value.
This argument is also moved to `spi_init()` because it is required to determine if `ssel` should be ignored or not. +| `spi_frequency(..., int hz)` | `spi_frequency(..., uint32_t hz)` | `hz` becomes unsigned as a negative does not make sense in this context. +| ̀`void spi_frequency(...)` | `uint32_t spi_frequency(...)` | ̀`spi_frequency()` now returns the actual frequency that the peripheral will be generating to allow a user adjust its strategy in case the target cannot be reached. +| `uint8_t spi_get_module(spi_t *)` | `SPIName spi_get_module(spi_pin_t *)` | This change will ease tracking of SPI instanced by using a know unique identifier for each peripheral.
This will be used by the Driver to Identify the various peripheral and allow multiple SPI transfer on different SPI peripherals.
The type change allows to call `spi_get_module()` before calling `spi_init()` +| - | `spi_get_capabilities(...)` | This method is introduce to help select, configure and run test appropriate to the tested target's peripheral. +| - | `#define SPI_COUNT (___)` | Introduce a macro defined in a header to tell the driver how many peripheral are available on this target. This information will be used by the driver to allocate a global mutex and owner table (one per peripheral). +| `spi_slave_*` and `spi_master_*` | only `spi_*` | Unifying `spi_slave` a `spi_master` API to provide a single way to interact with the peripheral. +| `spi_master_transfer(...)` | renamed to `spi_transfer_async(...)` | This change is to explicitly state that this method is meant for async operations. +| `spi_master_block_write(...)` | renamed to `spi_transfer(...)` | This change is reflect more accurately what the method is actually doing.
`_block_write` only implies emission where the in fact that it may also receive data. +| `spi_transfer_async(...)` and `spi_transfer(...);` | changes in arguments list | They now share the same list of arguments (`_async` as some more to handle the asynchronous part) :
`spi_t *obj, const void *tx, uint32_t tx_len, void *rx, uint32_t rx_len, const void *fill_symbol` +| `spi_transfer_async(..., uint32_t handler, ...)` | becomes `spi_transfer_async(..., spi_async_handler_f handler)`
with `void (*spi_async_handler_f)(spi_t *obj, void *ctx, spi_async_event_t event)` | This change sanitizes the function pointer +| - | introduce `void *ctx` as an argument of `spi_transfer_async` | This argument shall be passed to the invocation of the event handler. +| `void spi_transfer_async(...)` | `bool spi_transfer_async(...)` | `spi_transfer_async(...)` now returns a true if the transfer was scheduled and false otherwise.
E.g.: if the peripheral is already busy with another transfer. +| `spi_transfer_async(..., uint32_t event, ...)` | argument removed | the callback will now be invoked on any event with the event as an argument. +| `spi_busy(spi)` | removed | This method is never used outside of some of the hal implementations. +| `spi_master_write(...)` `spi_slave_write(...)` `spi_slave_read(...)` `spi_slave_receive(...)` | removed | Writing data symbol by symbol is very inefficient and should not be encouraged. It can still be achieved by passing a pointer to the symbol to `spi_transfer`. +| `spi_active(spit_t *obj)` | removed | as the async call back is now always invoked on async operation termination (unless cancelled), this status can be tracked from driver layer without any hal request. +| `uint32_t spi_irq_handler_asynch(spi_t *obj)` | removed | as the event is now passed as an argument to the callback this method is no longer required. + +### The new API + +```c +typedef struct { + /** Minimum frequency supported must be set by target device and it will be assessed during + * testing. + */ + uint32_t minimum_frequency; + /** Maximum frequency supported must be set by target device and it will be assessed during + * testing. + */ + uint32_t maximum_frequency; + /** Each bit represents the corresponding symbol length. lsb => 1bit, msb => 32bits. + * + * For example, if the peripheral supports 8 bits, 12bits to 16bits and 32bits, + * the value should be 0x8000F880. + */ + uint32_t symbol_length; + bool support_slave_mode; /**< If true, the device can handle SPI slave mode using hardware management on the specified ssel pin. */ + bool half_duplex; /**< If true, the device also supports SPI transmissions using only 3 wires. */ +} spi_capabilities_t; + +typedef struct { + /** + * count of symbol clocked/transferred on the bus. + */ + uint32_t transfered; + bool error:1; +} spi_async_event_t; + +typedef enum _spi_mode_t { + SPI_MODE_IDLE_LOW_SAMPLE_FIRST_EDGE, + SPI_MODE_IDLE_LOW_SAMPLE_SECOND_EDGE, + SPI_MODE_IDLE_HIGH_SAMPLE_FIRST_EDGE, + SPI_MODE_IDLE_HIGH_SAMPLE_SECOND_EDGE, +} spi_mode_t; + +typedef enum _spi_bit_ordering_t { + SPI_BIT_ORDERING_MSB_FIRST, + SPI_BIT_ORDERING_LSB_FIRST, +} spi_bit_ordering_t; + +typedef void (*spi_async_handler_f)(spi_t *obj, void *ctx, spi_async_event_t *event); + +/** + * Returns a variant of the SPIName enum uniquely identifying a SPI peripheral of the device. + */ +SPIName spi_get_module(PinName mosi, PinName miso, PinName mclk); +/** + * Fills the given spi_capabilities_t structure with the capabilities of the given peripheral. + */ +void spi_get_capabilities(SPIName name, PinName ssel, spi_capabilities_t *cap); + +void spi_init(spi_t *obj, bool is_slave, PinName mosi, PinName miso, PinName mclk, PinName ssel); +void spi_format(spi_t *obj, uint8_t bits, spi_mode_t mode, spi_bit_ordering_t bit_ordering); +uint32_t spi_frequency(spi_t *obj, uint32_t hz); +uint32_t spi_transfer(spi_t *obj, const void *tx, uint32_t tx_len, void *rx, uint32_t rx_len, const void *fill_symbol); +bool spi_transfer_async(spi_t *obj, const void *tx, uint32_t tx_len, void *rx, uint32_t rx_len, const void *fill_symbol, spi_async_handler_f handler, void *ctx, DMAUsage hint); +void spi_transfer_async_abort(spi_t *obj); +void spi_free(spi_t *obj); +``` + +## Behaviours +### Defined Behaviours + +- `spi_get_module()` returns the `SPIName` unique identifier to the peripheral associated to this SPI channel. +- `spi_get_capabilities()` fills the given `spi_capabilities_t` instance +- `spi_get_capabilities()` should consider the `ssel` pin when evaluation the `support_slave_mode` capability. + If the given `ssel` pin cannot be managed by hardware in slave mode, `support_slave_mode` should be false. +- At least a symbol width of 8bit must be supported. +- The supported frequency range must include the range [0.2..2] MHz. +- The shortest part of the duty cycle must not be shorter than 50% of the expected period. +- `spi_init()` initializes the pins leaving the configuration registers unchanged. +- `spi_init()` if `is_slave` is false: + - if `ssel` is `NC` the hal implementation ignores this pin. + - if `ssel` is not `NC` then the hal implementation owns the pin and its management. +- When managed by the hal implementation, `ssel` is always considered active low. +- When the hardware supports the half-duplex (3-wire) mode, if `miso` (exclusive) or `mosi` is missing in any function that expects pins, the bus is assumed to be half-duplex. +- `spi_free()` resets the pins to their default state. +- `spi_free()` disables the peripheral clock. +- `spi_format()` sets : + - the number of bits per symbol + - the mode : + 0. Clock idle state is *low*, data are sampled when the clock becomes *active* (polarity = 0, phase = 0) + 1. Clock idle state is *low*, data are sampled when the clock becomes *inactive* (polarity = 0, phase = 1) + 2. Clock idle state is *high*, data are sampled when the clock becomes *active* (polarity = 1, phase = 0) + 3. Clock idle state is *high*, data are sampled when the clock becomes *inactive* (polarity = 1, phase = 1) + - the bit ordering (lsb/msb first). +- `spi_format()` updates the configuration of the peripheral except the baud rate generator. +- `spi_frequency()` sets the frequency to use during the transfer. +- `spi_frequency()` returns the actual frequency that will be used. +- `spi_frequency()` updates the baud rate generator leaving other configurations unchanged. +- `spi_init()`, `spi_frequency()` and `spi_format()` must be called at least once each before initiating any transfer. +- `spi_transfer()` : + - writes `tx_len` symbols to the bus. + - reads `rx_len` symbols from the bus. + - if `rx` is NULL then inputs are discarded. + - if `tx` is NULL then `fill_symbol` is used instead. + - returns the number of symbol clocked on the bus during this transfer. + - expects symbols types to be the closest stdint type bigger or equal to its size following the platform's endianness. + e.g.: + - 7bits => uint8_t + - 15bits => uint16_t + - 16bits => uint16_t + - 17bits => uint32_t + - In Full-duplex mode : + - if `rx_len` > `tx_len` then it sends `(rx_len-tx_len)` additional `fill_symbol` to the bus. + - In Half-duplex mode : + - as master, `spi_transfer()` sends `tx_len` symbols and then reads `rx_len` symbols. + - as slave, `spi_transfer()` receives `rx_len` symbols and then sends `tx_len` symbols. +- `spi_transter_async()` schedules a transfer to be process the same way `spi_transfer()` would have but asynchronously. +- `spi_transter_async()` returns immediately with a boolean indicating whether the transfer was successfully scheduled or not. +- The callback given to `spi_transfer_async()` is invoked when the transfer completes (with a success or an error). +- `spi_transfer_async()` saves the handler and the `ctx` pointer. +- The `ctx` is passed to the callback on transfer completion. +- Unless if the transfer is aborted, the callback is invoked on completion. The completion maybe when all symbols have been transmitted + or when in slave mode the master de-asserts the chip select. +- The `spi_transfer_async()` function may use the `DMAUsage` hint to select the appropriate async algorithm. +- The `spi_async_event_t` must be filled with the number of symbol clocked on the bus during this transfer and a boolean value indicated if an error has occurred. +- `spi_transfer_async_abort()` aborts an on-going async transfer. + +### Undefined Behaviours +- Calling `spi_init()` multiple times on the same `spi_t` without `spi_free()`'ing it first. +- Calling any method other than `spi_init()` on a non-initialized or freed `spi_t`. +- Passing both `miso` and `mosi` as `NC` to `spi_get_module` or `spi_init`. +- Passing `miso` or `mosi` as `NC` on target that does not support half-duplex mode. +- Passing `mclk` as `NC` to `spi_get_module` or `spi_init`. +- Passing an invalid pointer as `cap` to `spi_get_capabilities`. +- Passing pins that cannot be on the same peripheral. +- Passing an invalid pointer as `obj` to any method. +- Giving a `ssel` pin to `spi_init()` when using in master mode. + SS must be managed by hardware in slave mode and must **NOT** be managed by hardware in master mode. +- Setting a frequency outside of the range given by `spi_get_capabilities()`. +- Setting a frequency in slave mode. +- Setting `bits` in `spi_format` to a value out of the range given by `spi_get_capabilities()`. +- Passing an invalid pointer as `fill_symbol` to `spi_transfer` and `spi_transfer_async` while they would be required by the transfer (`rx_len != tx_len` or `tx==NULL`). +- Passing an invalid pointer as `handler` to `spi_transfer_async`. +- Calling `spi_transfer_async_abort()` while no async transfer is being processed (no transfer or a synchronous transfer). +- In half-duplex mode, any mechanism (if any is present) to detect or prevent collision is implementation defined. + +### Updated flow +The IRQ flow has slightly changed. In the new API the flow is as follow : +![IRQ flow](spi_irq_flow.png) + +## Testing +Any regression will be captured as tests are created and added to CI to cover the newly defined behaviours. However, due to hardware limitations, it will not be possible to test them all in CI. +Some examples will be created in order to cover the remaining behaviours and use-cases. Those examples will be run manually before releases. + +## Impact on partners' implementations + +The new API does not impact partners much as most of them factorise HAL implementation to family level. +For example : + +| Partner | #of Implementation | details | +| --- | --- | --- | +| ST | 1 | - | +| Silicon Labs | 1 | EFM32 family | +| Analog device | 2 | They differ by ~20LoC that enable the ability to set the polarity and phase of the clock. They could most probably be merged as a single implementation. | +| Nordic | 2 | The first one for NRF51, the other for NRF52.
The functions calls are from what I have seen the same, this could be for Nordic a good opportunity to unify their SPI HAL implementation and bump NRF51’s SDK version. | +| Atmel | 2 | - one for the cortex-m4
- one for the cortex-m0+
This seems reasonable. | +| Nuvoton | 4 | implementation for 451, 472 and 480 are similar and look like the evolution of the 451.
NANO100's implementation has more "tweaks" but is really similar to the others, they could probably all be merged as 1. | +| Maxim | 6 | There's actually only 3 different implementations as :
- MAX32620
- MAX32600 = MAX32610
- MAX32630 = MAX32625 = MAX32620C | +| NXP | 13 | Most of them differ by the pin mapping that is provided in spi_api.c instead of `PeripheralPins.c` as it is done on most of other targets.
This number could be easily reduced to 2, maybe 4 in the very worst case. This would still be a huge maintenance gain for NXP DRY’ing their code base. | +| Freescale | 15 | Some of them are exactly the same. This number could be reduce to something around 5 implementation mostly due to the large number of µC generation (K20/KLXX/MCUXPresso etc). | + +The partner the most impacted by these changes is definitely Freescale/NXP but overall, in addition to the few bonus added by this new API, it would give partners' team a good opportunity to overhaul their implementations and factorise the code base. + +## Drawbacks +-- + +## Alternative solutions + +## Questions + +- To keep consistency between peripherals in terms of design pattern, do we want to expose a peripheral or a communication channel ? + The current SPI driver implementation has 1 instance per slave while i²c driver implementation has 1 instance per peripheral. + - SPI addresses slave in `spi_init` using the chip select. + - I²C addresses slave in transfer functions using the address. + See also: [#7358](https://github.com/ARMmbed/mbed-os/issues/7358) + + The proposed HAL API here allow the SPI driver to have 1 instance per slave while keeping 1 spi_t per peripheral. This gives the user the best of both world. diff --git a/docs/design-documents/hal/spi_irq_flow.png b/docs/design-documents/hal/spi_irq_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..315c4e373b0716f89e9aa08163250ecf9e16f42b GIT binary patch literal 15419 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 literal 0 HcmV?d00001