* Based on code from LTIB:
* (C) Copyright 2009-2010 Freescale Semiconductor, Inc.
*
- * See file CREDITS for list of people who contributed to this
- * project.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
- * MA 02111-1307 USA
+ * SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <asm/arch/sys_proto.h>
/* Maximum fixed count */
-#if defined(CONFIG_MX23)
+#if defined(CONFIG_SOC_MX23)
#define TIMER_LOAD_VAL 0xffff
-#elif defined(CONFIG_MX28)
+#elif defined(CONFIG_SOC_MX28)
#define TIMER_LOAD_VAL 0xffffffff
#endif
DECLARE_GLOBAL_DATA_PTR;
-#define timestamp (gd->arch.tbl)
-#define lastdec (gd->arch.lastinc)
+/* Enable this to verify that the code can correctly
+ * handle the timer rollover
+ */
+/* #define DEBUG_TIMER_WRAP */
+
+#ifdef DEBUG_TIMER_WRAP
+/*
+ * Let the timer wrap 15 seconds after start to catch misbehaving
+ * timer related code early
+ */
+#define TIMER_START (-time_to_tick(15 * CONFIG_SYS_HZ))
+#else
+#define TIMER_START 0UL
+#endif
/*
* This driver uses 1kHz clock source.
return time * (MXS_INCREMENTER_HZ / CONFIG_SYS_HZ);
}
-/* Calculate how many ticks happen in "us" microseconds */
-static inline unsigned long us_to_tick(unsigned long us)
-{
- return (us * MXS_INCREMENTER_HZ) / 1000000;
-}
-
int timer_init(void)
{
struct mxs_timrot_regs *timrot_regs =
mxs_reset_block(&timrot_regs->hw_timrot_rotctrl_reg);
/* Set fixed_count to 0 */
-#if defined(CONFIG_MX23)
+#if defined(CONFIG_SOC_MX23)
writel(0, &timrot_regs->hw_timrot_timcount0);
-#elif defined(CONFIG_MX28)
+#elif defined(CONFIG_SOC_MX28)
writel(0, &timrot_regs->hw_timrot_fixed_count0);
#endif
TIMROT_TIMCTRLn_SELECT_1KHZ_XTAL,
&timrot_regs->hw_timrot_timctrl0);
- /* Set fixed_count to maximal value */
-#if defined(CONFIG_MX23)
+#ifndef DEBUG_TIMER_WRAP
+ /* Set fixed_count to maximum value */
+#if defined(CONFIG_SOC_MX23)
writel(TIMER_LOAD_VAL - 1, &timrot_regs->hw_timrot_timcount0);
-#elif defined(CONFIG_MX28)
+#elif defined(CONFIG_SOC_MX28)
writel(TIMER_LOAD_VAL, &timrot_regs->hw_timrot_fixed_count0);
#endif
-
+#else /* DEBUG_TIMER_WRAP */
+ /* Set fixed_count so that the counter will wrap after 20 seconds */
+#if defined(CONFIG_SOC_MX23)
+ writel(20 * MXS_INCREMENTER_HZ - 1, &timrot_regs->hw_timrot_timcount0);
+#elif defined(CONFIG_SOC_MX28)
+ writel(20 * MXS_INCREMENTER_HZ,
+ &timrot_regs->hw_timrot_fixed_count0);
+#endif
+ gd->arch.lastinc = TIMER_LOAD_VAL - 20 * MXS_INCREMENTER_HZ;
+
+ /* Make the usec counter roll over 30 seconds after startup */
+ writel(-30000000, MXS_HW_DIGCTL_MICROSECONDS);
+#endif /* DEBUG_TIMER_WRAP */
+ writel(TIMROT_TIMCTRLn_UPDATE,
+ &timrot_regs->hw_timrot_timctrl0_clr);
+#ifdef DEBUG_TIMER_WRAP
+ /* Set fixed_count to maximum value for subsequent loads */
+#if defined(CONFIG_SOC_MX23)
+ writel(20 * MXS_INCREMENTER_HZ - 1, &timrot_regs->hw_timrot_timcount0);
+#elif defined(CONFIG_SOC_MX28)
+ writel(TIMER_LOAD_VAL, &timrot_regs->hw_timrot_fixed_count0);
+#endif
+#endif /* DEBUG_TIMER_WRAP */
+ gd->arch.timer_rate_hz = MXS_INCREMENTER_HZ;
+ gd->arch.tbl = TIMER_START;
+ gd->arch.tbu = 0;
return 0;
}
+/* Note: This function works correctly for TIMER_LOAD_VAL == 0xffffffff!
+ * The rollover is handled automagically due to the properties of
+ * two's complement arithmetic.
+ * For any other value of TIMER_LOAD_VAL the calculations would have
+ * to be done modulus(TIMER_LOAD_VAL + 1).
+ */
unsigned long long get_ticks(void)
{
struct mxs_timrot_regs *timrot_regs =
(struct mxs_timrot_regs *)MXS_TIMROT_BASE;
- uint32_t now;
-
- /* Current tick value */
-#if defined(CONFIG_MX23)
+ unsigned long now;
+#if defined(CONFIG_SOC_MX23)
/* Upper bits are the valid ones. */
now = readl(&timrot_regs->hw_timrot_timcount0) >>
TIMROT_RUNNING_COUNTn_RUNNING_COUNT_OFFSET;
-#elif defined(CONFIG_MX28)
- now = readl(&timrot_regs->hw_timrot_running_count0);
+#elif defined(CONFIG_SOC_MX28)
+ /* The timer is counting down, so subtract the register value from
+ * the counter period length (implicitly 2^32) to get an incrementing
+ * timestamp
+ */
+ now = -readl(&timrot_regs->hw_timrot_running_count0);
+#else
+#error "Don't know how to read timrot_regs"
#endif
+ ulong inc = now - gd->arch.lastinc;
- if (lastdec >= now) {
- /*
- * normal mode (non roll)
- * move stamp forward with absolut diff ticks
- */
- timestamp += (lastdec - now);
- } else {
- /* we have rollover of decrementer */
- timestamp += (TIMER_LOAD_VAL - now) + lastdec;
-
- }
- lastdec = now;
-
- return timestamp;
+ if (gd->arch.tbl + inc < gd->arch.tbl)
+ gd->arch.tbu++;
+ gd->arch.tbl += inc;
+ gd->arch.lastinc = now;
+ return ((unsigned long long)gd->arch.tbu << 32) | gd->arch.tbl;
}
ulong get_timer_masked(void)
ulong get_timer(ulong base)
{
- return get_timer_masked() - base;
+ /* NOTE: time_to_tick(base) is required to correctly handle rollover! */
+ return tick_to_time(get_ticks() - time_to_tick(base));
}
/* We use the HW_DIGCTL_MICROSECONDS register for sub-millisecond timer. */
-#define MXS_HW_DIGCTL_MICROSECONDS 0x8001c0c0
-
void __udelay(unsigned long usec)
{
- uint32_t old, new, incr;
- uint32_t counter = 0;
-
- old = readl(MXS_HW_DIGCTL_MICROSECONDS);
-
- while (counter < usec) {
- new = readl(MXS_HW_DIGCTL_MICROSECONDS);
-
- /* Check if the timer wrapped. */
- if (new < old) {
- incr = 0xffffffff - old;
- incr += new;
- } else {
- incr = new - old;
- }
-
- /*
- * Check if we are close to the maximum time and the counter
- * would wrap if incremented. If that's the case, break out
- * from the loop as the requested delay time passed.
+ struct mxs_digctl_regs *digctl_regs = (void *)MXS_DIGCTL_BASE;
+ u32 start = readl(&digctl_regs->hw_digctl_microseconds);
+
+ while (readl(&digctl_regs->hw_digctl_microseconds) - start <= usec)
+ /* use '<=' to guarantee a delay of _at least_
+ * the given number of microseconds.
+ * No need for fancy rollover checks
+ * Two's complement arithmetic applied correctly
+ * does everything that's needed automagically!
*/
- if (counter + incr < counter)
- break;
-
- counter += incr;
- old = new;
- }
+ ;
}
ulong get_tbclk(void)
{
- return MXS_INCREMENTER_HZ;
+ return gd->arch.timer_rate_hz;
}