drivers: timer: Add per-core ARM architected timer

ARM cores may have a per-core architected timer, which provides per-cpu
timers, attached to a GIC to deliver its per-processor interrupts via
PPIs. This is the most common case supported by QEMU in the virt
platform.

This patch introduces support for this timer abstracting the way the
timer registers are actually accessed. This is needed because different
architectures (for example ARMv7-R vs ARMv8-A) use different registers
and even the same architecture (ARMv8-A) can actually use different
timers (ELx physical timers vs ELx virtual timers).

So we introduce the common driver here but the actual SoC / architecture
/ board must provide the three helpers (arm_arch_timer_set_compare(),
arm_arch_timer_toggle(), arm_arch_timer_count()) using an header file
imported through the arch/cpu.h header file.

Signed-off-by: Carlo Caione <ccaione@baylibre.com>
This commit is contained in:
Carlo Caione 2019-10-27 16:12:58 +00:00 committed by Anas Nashif
commit 6f36300219
7 changed files with 156 additions and 0 deletions

View file

@ -186,6 +186,7 @@
/drivers/spi/spi_ll_stm32.* @superna9999
/drivers/spi/spi_rv32m1_lpspi* @karstenkoenig
/drivers/timer/apic_timer.c @andrewboie
/drivers/timer/arm_arch_timer.c @carlocaione
/drivers/timer/cortex_m_systick.c @ioannisg
/drivers/timer/altera_avalon_timer_hal.c @wentongwu
/drivers/timer/riscv_machine_timer.c @nategraff-sifive @kgugala @pgielda

View file

@ -3,6 +3,7 @@
zephyr_sources( sys_clock_init.c)
zephyr_sources_ifdef(CONFIG_HPET_TIMER hpet.c)
zephyr_sources_ifdef(CONFIG_ARCV2_TIMER arcv2_timer0.c)
zephyr_sources_ifdef(CONFIG_ARM_ARCH_TIMER arm_arch_timer.c)
zephyr_sources_if_kconfig( loapic_timer.c)
zephyr_sources_if_kconfig( apic_timer.c)
zephyr_sources_ifdef(CONFIG_ALTERA_AVALON_TIMER altera_avalon_timer_hal.c)

View file

@ -111,6 +111,15 @@ config ARCV2_TIMER_IRQ_PRIORITY
This option specifies the IRQ priority used by the ARC timer. Lower
values have higher priority.
config ARM_ARCH_TIMER
bool "ARM architected timer"
depends on GIC
select TICKLESS_CAPABLE
help
This module implements a kernel device driver for the ARM architected
timer which provides per-cpu timers attached to a GIC to deliver its
per-processor interrupts via PPIs.
config CORTEX_M_SYSTICK
bool "Cortex-M SYSTICK timer"
depends on CPU_CORTEX_M_HAS_SYSTICK

View file

@ -0,0 +1,109 @@
/*
* Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <drivers/timer/arm_arch_timer.h>
#include <drivers/timer/system_timer.h>
#include <sys_clock.h>
#include <spinlock.h>
#include <arch/cpu.h>
#define CYC_PER_TICK ((u32_t)((u64_t)sys_clock_hw_cycles_per_sec() \
/ (u64_t)CONFIG_SYS_CLOCK_TICKS_PER_SEC))
#define MAX_TICKS ((0xffffffffu - CYC_PER_TICK) / CYC_PER_TICK)
#define MIN_DELAY (1000)
static struct k_spinlock lock;
static volatile u64_t last_cycle;
static void arm_arch_timer_compare_isr(void *arg)
{
ARG_UNUSED(arg);
k_spinlock_key_t key = k_spin_lock(&lock);
u64_t curr_cycle = arm_arch_timer_count();
u32_t delta_ticks = (u32_t)((curr_cycle - last_cycle) / CYC_PER_TICK);
last_cycle += delta_ticks * CYC_PER_TICK;
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL) ||
IS_ENABLED(CONFIG_QEMU_TICKLESS_WORKAROUND)) {
u64_t next_cycle = last_cycle + CYC_PER_TICK;
if ((s64_t)(next_cycle - curr_cycle) < MIN_DELAY) {
next_cycle += CYC_PER_TICK;
}
arm_arch_timer_set_compare(next_cycle);
}
k_spin_unlock(&lock, key);
z_clock_announce(IS_ENABLED(CONFIG_TICKLESS_KERNEL) ? delta_ticks : 1);
}
int z_clock_driver_init(struct device *device)
{
ARG_UNUSED(device);
IRQ_CONNECT(ARM_ARCH_TIMER_IRQ, 0, arm_arch_timer_compare_isr, 0,
ARM_TIMER_FLAGS);
arm_arch_timer_set_compare(arm_arch_timer_count() + CYC_PER_TICK);
arm_arch_timer_enable(true);
irq_enable(ARM_ARCH_TIMER_IRQ);
return 0;
}
void z_clock_set_timeout(s32_t ticks, bool idle)
{
ARG_UNUSED(idle);
#if defined(CONFIG_TICKLESS_KERNEL) && !defined(CONFIG_QEMU_TICKLESS_WORKAROUND)
if (idle) {
return;
}
ticks = (ticks == K_FOREVER) ? MAX_TICKS : ticks;
ticks = MAX(MIN(ticks - 1, (s32_t)MAX_TICKS), 0);
k_spinlock_key_t key = k_spin_lock(&lock);
u64_t curr_cycle = arm_arch_timer_count();
u32_t req_cycle = ticks * CYC_PER_TICK;
/* Round up to next tick boundary */
req_cycle += (u32_t)(curr_cycle - last_cycle) + (CYC_PER_TICK - 1);
req_cycle = (req_cycle / CYC_PER_TICK) * CYC_PER_TICK;
if ((s32_t)(req_cycle + last_cycle - curr_cycle) < MIN_DELAY) {
req_cycle += CYC_PER_TICK;
}
arm_arch_timer_set_compare(req_cycle + last_cycle);
k_spin_unlock(&lock, key);
#endif
}
u32_t z_clock_elapsed(void)
{
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
return 0;
}
k_spinlock_key_t key = k_spin_lock(&lock);
u32_t ret = ((u32_t)arm_arch_timer_count() - (u32_t)last_cycle)
/ CYC_PER_TICK;
k_spin_unlock(&lock, key);
return ret;
}
u32_t z_timer_cycle_get_32(void)
{
return (u32_t)arm_arch_timer_count();
}

View file

@ -0,0 +1,13 @@
description: >
per-core ARM architected timer
compatible: "arm,arm-timer"
include: base.yaml
properties:
interrupts:
required: true
label:
required: true

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2019 Carlo Caione <ccaione@baylibre.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_TIMER_ARM_ARCH_TIMER_H_
#define ZEPHYR_INCLUDE_DRIVERS_TIMER_ARM_ARCH_TIMER_H_
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <zephyr/types.h>
#define ARM_TIMER_SECURE_IRQ DT_ARM_ARM_TIMER_TIMER_IRQ_0
#define ARM_TIMER_NON_SECURE_IRQ DT_ARM_ARM_TIMER_TIMER_IRQ_1
#define ARM_TIMER_VIRTUAL_IRQ DT_ARM_ARM_TIMER_TIMER_IRQ_2
#define ARM_TIMER_HYP_IRQ DT_ARM_ARM_TIMER_TIMER_IRQ_3
#define ARM_TIMER_FLAGS IRQ_TYPE_EDGE
#endif /* ZEPHYR_INCLUDE_DRIVERS_TIMER_ARM_ARCH_TIMER_H_ */

View file

@ -49,6 +49,8 @@
*/
#if defined(CONFIG_HPET_TIMER)
#define TICK_IRQ DT_INST_0_INTEL_HPET_IRQ_0
#elif defined(CONFIG_ARM_ARCH_TIMER)
#define TICK_IRQ ARM_ARCH_TIMER_IRQ
#elif defined(CONFIG_APIC_TIMER)
#define TICK_IRQ CONFIG_APIC_TIMER_IRQ
#elif defined(CONFIG_LOAPIC_TIMER)