drivers: systick: Fix Cortex-M systick jumping forward and back again

The existing implementation did not properly
handle when `SysTick->VAL` is zero.

This caused three subtle edge cases:
* val1=0,COUNTFLAG=0,val2=0
  This should result in no cycles elapsed,
  however `(last_load - val2) = last_load`.
  So an extra `last_load` cycles was returned.
* val1=0,COUNTFLAG=0,val2=(last_load-1)
  This should result in 1 cycle elapsed,
  however `val1 < val2` so an extra `last_load`
  cycles was returned.
* val1=[2,1,0],COUNTFLAG=1,val2=0
  This should result in `last_load` cycles elapsed.
  However, `last_load * 2` cycles was returned.

To fix the calculation, val1 and val2 are first
wrapped/realigned from [0:last_load-1] to [1:last_load].

Tidy comments to better reflect the SysTick
behaviour and link reference manuals.

Signed-off-by: Grant Ramsay <gramsay@enphaseenergy.com>
This commit is contained in:
Grant Ramsay 2023-10-01 12:39:47 +13:00 committed by Fabio Baltieri
commit e032f9520d

View file

@ -101,10 +101,19 @@ static uint32_t elapsed(void)
uint32_t ctrl = SysTick->CTRL; /* B */
uint32_t val2 = SysTick->VAL; /* C */
/* SysTick behavior: The counter wraps at zero automatically,
* setting the COUNTFLAG field of the CTRL register when it
* does. Reading the control register automatically clears
* that field.
/* SysTick behavior: The counter wraps after zero automatically.
* The COUNTFLAG field of the CTRL register is set when it
* decrements from 1 to 0. Reading the control register
* automatically clears that field. When a timer is started,
* count begins at zero then wraps after the first cycle.
* Reference:
* Armv6-m (B3.3.1) https://developer.arm.com/documentation/ddi0419
* Armv7-m (B3.3.1) https://developer.arm.com/documentation/ddi0403
* Armv8-m (B11.1) https://developer.arm.com/documentation/ddi0553
*
* First, manually wrap/realign val1 and val2 from [0:last_load-1]
* to [1:last_load]. This allows subsequent code to assume that
* COUNTFLAG and wrapping occur on the same cycle.
*
* If the count wrapped...
* 1) Before A then COUNTFLAG will be set and val1 >= val2
@ -115,6 +124,13 @@ static uint32_t elapsed(void)
* So the count in val2 is post-wrap and last_load needs to be
* added if and only if COUNTFLAG is set or val1 < val2.
*/
if (val1 == 0) {
val1 = last_load;
}
if (val2 == 0) {
val2 = last_load;
}
if ((ctrl & SysTick_CTRL_COUNTFLAG_Msk)
|| (val1 < val2)) {
overflow_cyc += last_load;