Skip to content

Commit

Permalink
cpu/stm32/periph_spi: improve clock divider calculation
Browse files Browse the repository at this point in the history
With only 8 possible clock dividers, we can just loop over the values
and shift the clock. In addition to being much easier to read, using
shifts over divisions can be a lot faster on CPUs without hardware
division.

In addition an `assert()` is added that checks if the API contract
regarding the SPI frequency is honored. If the requested clock is too
low to be generated, we should rather have a blown assertion than
hard to trace communication errors.
  • Loading branch information
maribu committed Nov 23, 2023
1 parent 1d00706 commit 2fc1fdc
Showing 1 changed file with 19 additions and 29 deletions.
48 changes: 19 additions & 29 deletions cpu/stm32/periph/spi.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

#include <assert.h>

#include "bitarithm.h"
#include "mutex.h"
#include "periph/gpio.h"
#include "periph/spi.h"
Expand Down Expand Up @@ -80,33 +79,24 @@ static inline bool _use_dma(const spi_conf_t *conf)
}
#endif

/**
* @brief Multiplier for clock divider calculations
*
* Makes the divider calculation fixed point
*/
#define SPI_APB_CLOCK_SHIFT (4U)
#define SPI_APB_CLOCK_MULT (1U << SPI_APB_CLOCK_SHIFT)

static uint8_t _get_clkdiv(const spi_conf_t *conf, uint32_t clock)
{
uint32_t bus_clock = periph_apb_clk(conf->apbbus);
/* Shift bus_clock with SPI_APB_CLOCK_SHIFT to create a fixed point int */
uint32_t div = (bus_clock << SPI_APB_CLOCK_SHIFT) / (2 * clock);
DEBUG("[spi] clock: divider: %"PRIu32"\n", div);
/* Test if the divider is 2 or smaller, keeping the fixed point in mind */
if (div <= SPI_APB_CLOCK_MULT) {
return 0;
}
/* determine MSB and compensate back for the fixed point int shift */
uint8_t rounded_div = bitarithm_msb(div) - SPI_APB_CLOCK_SHIFT;
/* Determine if rounded_div is not a power of 2 */
if ((div & (div - 1)) != 0) {
/* increment by 1 to ensure that the clock speed at most the
* requested clock speed */
rounded_div++;
}
return rounded_div > BR_MAX ? BR_MAX : rounded_div;

uint8_t div = 0;
uint32_t divided_clock = bus_clock >> 1;
const uint8_t div_max = SPI_CR1_BR_Msk >> SPI_CR1_BR_Pos;
for (; (divided_clock > clock) && (div < div_max); div++) {
divided_clock >>= 1;
}

/* If the callers asks for an SPI frequency of at most x, bad things will
* happen if this cannot be met. So let's have a blown assertion
* rather than runtime failures that require a logic analyzer to
* debug. */
assert(divided_clock <= clock);

return div;
}

void spi_init(spi_t bus)
Expand Down Expand Up @@ -239,11 +229,11 @@ void spi_acquire(spi_t bus, spi_cs_t cs, spi_mode_t mode, spi_clk_t clk)
}
uint8_t br = dividers[bus];

DEBUG("[spi] acquire: requested clock: %"PRIu32", resulting clock: %"PRIu32
" BR divider: %u\n",
DEBUG("[spi] acquire: requested clock: %" PRIu32
" Hz, resulting clock: %" PRIu32 " Hz, BR divider: %u\n",
clk,
periph_apb_clk(spi_config[bus].apbbus)/(1 << (br + 1)),
br);
periph_apb_clk(spi_config[bus].apbbus) >> (br + 1),
(unsigned)br);

uint16_t cr1 = ((br << BR_SHIFT) | mode | SPI_CR1_MSTR | SPI_CR1_SPE);
/* Settings to add to CR2 in addition to SPI_CR2_SETTINGS */
Expand Down

0 comments on commit 2fc1fdc

Please sign in to comment.