mirror of
https://github.com/ARM-software/arm-trusted-firmware.git
synced 2025-04-19 02:54:24 +00:00

Delay functions like udelay() are often used to ensure that the necessary time passed to allow some asynchronous event to finish, such as the stabilization delay for a power rail. For these use cases it is not very problematic if the delay is slightly longer than requested, but it is critical that the delay must never be shorter. The current udelay() implementation contains two hazards that may cause the delay to be slightly shorter than intended: Firstly, the amount of ticks to wait is calculated with an integer division, which may cut off the last fraction of ticks needed. Secondly, the delay may be short by a fraction of a tick because we do not know whether the initial ("start") sample of the timer was near the start or near the end of the current tick. Thus, if the code intends to wait for one tick, it might read the timer value close to the end of the current tick and then read it again right after the start of the next tick, concluding that the duration of a full tick has passed when it in fact was just a fraction of it. This patch rounds up the division and always adds one extra tick to counteract both problems and ensure that delays will always be larger but never smaller than requested. Change-Id: Ic5fe5f858b5cdf3c0dbf3e488d4d5702d9569433 Signed-off-by: Julius Werner <jwerner@chromium.org>
68 lines
1.9 KiB
C
68 lines
1.9 KiB
C
/*
|
|
* Copyright (c) 2015, ARM Limited and Contributors. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <delay_timer.h>
|
|
#include <platform_def.h>
|
|
#include <utils_def.h>
|
|
|
|
/***********************************************************
|
|
* The delay timer implementation
|
|
***********************************************************/
|
|
static const timer_ops_t *ops;
|
|
|
|
/***********************************************************
|
|
* Delay for the given number of microseconds. The driver must
|
|
* be initialized before calling this function.
|
|
***********************************************************/
|
|
void udelay(uint32_t usec)
|
|
{
|
|
assert(ops != NULL &&
|
|
(ops->clk_mult != 0) &&
|
|
(ops->clk_div != 0) &&
|
|
(ops->get_timer_value != NULL));
|
|
|
|
uint32_t start, delta, total_delta;
|
|
|
|
assert(usec < UINT32_MAX / ops->clk_div);
|
|
|
|
start = ops->get_timer_value();
|
|
|
|
/* Add an extra tick to avoid delaying less than requested. */
|
|
total_delta = div_round_up(usec * ops->clk_div, ops->clk_mult) + 1;
|
|
|
|
do {
|
|
/*
|
|
* If the timer value wraps around, the subtraction will
|
|
* overflow and it will still give the correct result.
|
|
*/
|
|
delta = start - ops->get_timer_value(); /* Decreasing counter */
|
|
|
|
} while (delta < total_delta);
|
|
}
|
|
|
|
/***********************************************************
|
|
* Delay for the given number of milliseconds. The driver must
|
|
* be initialized before calling this function.
|
|
***********************************************************/
|
|
void mdelay(uint32_t msec)
|
|
{
|
|
udelay(msec*1000);
|
|
}
|
|
|
|
/***********************************************************
|
|
* Initialize the timer. The fields in the provided timer
|
|
* ops pointer must be valid.
|
|
***********************************************************/
|
|
void timer_init(const timer_ops_t *ops_ptr)
|
|
{
|
|
assert(ops_ptr != NULL &&
|
|
(ops_ptr->clk_mult != 0) &&
|
|
(ops_ptr->clk_div != 0) &&
|
|
(ops_ptr->get_timer_value != NULL));
|
|
|
|
ops = ops_ptr;
|
|
}
|