| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * (C) Copyright 2013 |
| * David Feng <fenghua@phytium.com.cn> |
| */ |
| |
| #include <bootstage.h> |
| #include <command.h> |
| #include <time.h> |
| #include <asm/global_data.h> |
| #include <asm/system.h> |
| #include <linux/bitops.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| /* |
| * Generic timer implementation of get_tbclk() |
| */ |
| unsigned long notrace get_tbclk(void) |
| { |
| unsigned long cntfrq; |
| asm volatile("mrs %0, cntfrq_el0" : "=r" (cntfrq)); |
| return cntfrq; |
| } |
| |
| #ifdef CONFIG_SYS_FSL_ERRATUM_A008585 |
| /* |
| * FSL erratum A-008585 says that the ARM generic timer counter "has the |
| * potential to contain an erroneous value for a small number of core |
| * clock cycles every time the timer value changes". |
| * This sometimes leads to a consecutive counter read returning a lower |
| * value than the previous one, thus reporting the time to go backwards. |
| * The workaround is to read the counter twice and only return when the value |
| * was the same in both reads. |
| * Assumes that the CPU runs in much higher frequency than the timer. |
| */ |
| unsigned long timer_read_counter(void) |
| { |
| unsigned long cntpct; |
| unsigned long temp; |
| |
| isb(); |
| asm volatile("mrs %0, cntpct_el0" : "=r" (cntpct)); |
| asm volatile("mrs %0, cntpct_el0" : "=r" (temp)); |
| while (temp != cntpct) { |
| asm volatile("mrs %0, cntpct_el0" : "=r" (cntpct)); |
| asm volatile("mrs %0, cntpct_el0" : "=r" (temp)); |
| } |
| |
| return cntpct; |
| } |
| #elif CONFIG_SUNXI_A64_TIMER_ERRATUM |
| /* |
| * This erratum sometimes flips the lower 11 bits of the counter value |
| * to all 0's or all 1's, leading to jumps forwards or backwards. |
| * Backwards jumps might be interpreted all roll-overs and be treated as |
| * huge jumps forward. |
| * The workaround is to check whether the lower 11 bits of the counter are |
| * all 0 or all 1, then discard this value and read again. |
| * This occasionally discards valid values, but will catch all erroneous |
| * reads and fixes the problem reliably. Also this mostly requires only a |
| * single read, so does not have any significant overhead. |
| * The algorithm was conceived by Samuel Holland. |
| */ |
| unsigned long timer_read_counter(void) |
| { |
| unsigned long cntpct; |
| |
| isb(); |
| do { |
| asm volatile("mrs %0, cntpct_el0" : "=r" (cntpct)); |
| } while (((cntpct + 1) & GENMASK(10, 0)) <= 1); |
| |
| return cntpct; |
| } |
| #else |
| /* |
| * timer_read_counter() using the Arm Generic Timer (aka arch timer). |
| */ |
| unsigned long notrace timer_read_counter(void) |
| { |
| unsigned long cntpct; |
| |
| isb(); |
| asm volatile("mrs %0, cntpct_el0" : "=r" (cntpct)); |
| |
| return cntpct; |
| } |
| #endif |
| |
| uint64_t notrace get_ticks(void) |
| { |
| unsigned long ticks = timer_read_counter(); |
| |
| gd->arch.tbl = ticks; |
| |
| return ticks; |
| } |
| |
| unsigned long usec2ticks(unsigned long usec) |
| { |
| ulong ticks; |
| if (usec < 1000) |
| ticks = ((usec * (get_tbclk()/1000)) + 500) / 1000; |
| else |
| ticks = ((usec / 10) * (get_tbclk() / 100000)); |
| |
| return ticks; |
| } |
| |
| ulong timer_get_boot_us(void) |
| { |
| u64 val = get_ticks() * 1000000; |
| |
| return val / get_tbclk(); |
| } |
| |
| #if CONFIG_IS_ENABLED(ARMV8_UDELAY_EVENT_STREAM) |
| void __udelay(unsigned long usec) |
| { |
| u64 target = get_ticks() + usec_to_tick(usec); |
| |
| /* At EL2 or above, use the event stream to avoid polling CNTPCT_EL0 so often */ |
| if (current_el() >= 2) { |
| u32 cnthctl_val; |
| const u8 event_period = 0x7; |
| |
| asm volatile("mrs %0, cnthctl_el2" : "=r" (cnthctl_val)); |
| asm volatile("msr cnthctl_el2, %0" : : "r" |
| (cnthctl_val | CNTHCTL_EL2_EVNT_EN | CNTHCTL_EL2_EVNT_I(event_period))); |
| |
| while (get_ticks() + (1ULL << event_period) <= target) |
| wfe(); |
| |
| /* Reset the event stream */ |
| asm volatile("msr cnthctl_el2, %0" : : "r" (cnthctl_val)); |
| } |
| |
| /* Fall back to polling CNTPCT_EL0 */ |
| while (get_ticks() <= target) |
| ; |
| } |
| #endif |