diff --git a/libplatsupport/CMakeLists.txt b/libplatsupport/CMakeLists.txt index ab7799f70..3e5cd8302 100644 --- a/libplatsupport/CMakeLists.txt +++ b/libplatsupport/CMakeLists.txt @@ -90,6 +90,10 @@ if(KernelPlatformQEMUArmVirt OR KernelPlatformQuartz64) endif() endif() +if(KernelPlatformHikey OR KernelPlatformFVP) + list(APPEND deps "src/driver/sp804/sp804_ltimer.c") +endif() + if(KernelPlatformExynos5422) list(APPEND deps src/mach/${LibPlatSupportMach}/clock/exynos_5422_clock.c) elseif(KernelPlatformExynos4 OR KernelPlatformExynos5410 OR KernelPlatformExynos5250) diff --git a/libplatsupport/include/platsupport/driver/sp804/sp804.h b/libplatsupport/include/platsupport/driver/sp804/sp804.h new file mode 100644 index 000000000..91c5823dd --- /dev/null +++ b/libplatsupport/include/platsupport/driver/sp804/sp804.h @@ -0,0 +1,118 @@ +/* + * Copyright 2022, HENSOLDT Cyber GmbH + * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#pragma once + +#include +#include + + +#define TCLR_ONESHOT BIT(0) +#define TCLR_VALUE_32 BIT(1) +/* Bit 2 and 3: Prescaler + * 00 = clock is divided by 1 (default) + * 01 = clock is divided by 16 + * 10 = clock is divided by 256 + * 11 = reserved, do not use. + * Bit 4: reserved + */ +#define TCLR_INTENABLE BIT(5) +#define TCLR_AUTORELOAD BIT(6) +#define TCLR_STARTTIMER BIT(7) + +/* + * The sp840 contains two identical timers: + * + * 0x000 - 0x01f sp804_regs_t timer1; + * 0x020 - 0x03f sp804_regs_t timer2; + * 0x040 - 0xefc Reserved + * 0xf00 TimerITCR + * 0xf04 TimerITOP + * 0xf08 - 0xfdf Reserved + * 0xfe0 - 0xfef TimerPeriphID[4] + * 0xff0 - 0xfff TimerPCellID[4] + */ + +#define SP804_TIMER2_OFFSET 0x20 + +typedef volatile struct { + uint32_t load; /* 0x00 */ + uint32_t value; /* 0x04 */ + uint32_t control; /* 0x08 TCLR */ + uint32_t intclr; /* 0x0c Interrupt Clear */ + uint32_t ris; /* 0x10 Raw interrupt Status */ + uint32_t mis; /* 0x14 Masked Interrupt Status */ + uint32_t bgload; /* 0x18 Background Load */ + uint32_t _rfu; /* 0x1c (unused) */ +} sp804_regs_t; + +compile_time_assert(sp804_timer_size , sizeof(sp804_regs_t) <= SP804_TIMER2_OFFSET); + +#define SP804_TIMER1_REGS(base) ((sp804_regs_t *)(base)) +#define SP804_TIMER2_REGS(base) ((sp804_regs_t *)((uintptr_t)(base) + SP804_TIMER2_OFFSET)) + +static void sp804_reset(sp804_regs_t *regs) +{ + regs->control = 0; +} + +static void sp804_stop(sp804_regs_t *regs) +{ + regs->control &= ~TCLR_STARTTIMER; +} + +static void sp804_start(sp804_regs_t *regs) +{ + regs->control |= TCLR_STARTTIMER; +} + +static bool sp804_is_irq_pending(sp804_regs_t *regs) +{ + /* return the raw interrupt status and not the masted interrupt status */ + return !!regs->ris; +} + +static bool sp804_clear_intr(sp804_regs_t *regs) +{ + regs->intclr = 0x1; +} + +static uint32_t sp804_get_ticks(sp804_regs_t *regs) +{ + return regs->value; +} + +static void sp804_set_timeout(sp804_regs_t *regs, uint32_t ticks, + bool is_periodic, bool enable_intr) +{ + regs->control = 0; /* stop timer */ + + /* If 'ticks' is 0, then writing to 'load' will generate an interrupt + * immediately. + * + * The "Hikey Application Processor Function Description" says in + * section 2.3: + * The minimum valid value of TIMERN_LOAD is 1. If 0 is written to + * TIMERN_LOAD, a timing interrupt is generated immediately. + * TIMERN_BGLOAD is an initial count value register in periodic mode. In + * periodic mode, when the value of TIMERN_BGLOAD is updated, the + * value of TIMERN_LOAD is changed to that of TIMERN_BGLOAD. However, + * the timer counter does not restart counting. After the counter + * decreases to 0, the value of TIMERN_LOAD (that is, + * the value of TIMERN_BGLOAD) is reloaded to the counter. + * + * In other words, for periodic mode, load BGLOAD first, then write to + * LOAD. For oneshot mode, only write to LOAD. For good measure, write 0 + * to BGLOAD. + */ + regs->bgload = is_periodic ? ticks : 0; + regs->load = ticks; + + /* The TIMERN_VALUE register is read-only. */ + regs->control = TCLR_STARTTIMER | TCLR_VALUE_32 + | (is_periodic ? TCLR_AUTORELOAD : TCLR_ONESHOT) + | (enable_intr ? TCLR_INTENABLE : 0); +} diff --git a/libplatsupport/include/platsupport/driver/sp804/sp804_ltimer.h b/libplatsupport/include/platsupport/driver/sp804/sp804_ltimer.h new file mode 100644 index 000000000..9f58d92e8 --- /dev/null +++ b/libplatsupport/include/platsupport/driver/sp804/sp804_ltimer.h @@ -0,0 +1,17 @@ +/* + * Copyright 2022, HENSOLDT Cyber GmbH + * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +#include "../../ltimer.h" + +int ltimer_sp804_init(ltimer_t *ltimer, const char *ftd_path, uint64_t freq, + ps_io_ops_t ops, ltimer_callback_fn_t callback, + void *callback_token); diff --git a/libplatsupport/plat_include/fvp/platsupport/plat/sp804.h b/libplatsupport/plat_include/fvp/platsupport/plat/sp804.h deleted file mode 100644 index 3d61f1436..000000000 --- a/libplatsupport/plat_include/fvp/platsupport/plat/sp804.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) - * - * SPDX-License-Identifier: BSD-2-Clause - */ -#pragma once - -#include -#include -#include - -/* Each SP804 has two timers, but we only use one timer on eace device page. - * This is because the two timers on the same page share the same interrupt, - * and using one timer on each page saves us from identifying the sources of - * interrupts. - * */ -#define SP804_TIMER1_PATH "/soc/timer@1c110000" -#define SP804_TIMER2_PATH "/soc/timer@1c120000" - -#define SP804_REG_CHOICE 0 -#define SP804_IRQ_CHOICE 0 - -static UNUSED timer_properties_t sp804_timer_props = { - .upcounter = false, - .timeouts = true, - .absolute_timeouts = false, - .relative_timeouts = true, - .periodic_timeouts = true, - .bit_width = 32, - .irqs = 1 -}; - -typedef volatile struct sp804_regs { - uint32_t load; - uint32_t value; - uint32_t control; - uint32_t intclr; - uint32_t ris; - uint32_t mis; - uint32_t bgload; -} sp804_regs_t; - -typedef struct { - /* set in init */ - ps_io_ops_t ops; - ltimer_callback_fn_t user_cb_fn; - void *user_cb_token; - ltimer_event_t user_cb_event; /* what are we being used for? */ - - /* set in fdt helper */ - volatile sp804_regs_t *sp804_map; - pmem_region_t pmem; - irq_id_t irq_id; - - /* set in setup */ - uint32_t time_h; -} sp804_t; - -typedef struct { - const char *fdt_path; - ltimer_callback_fn_t user_cb_fn; - void *user_cb_token; - ltimer_event_t user_cb_event; -} sp804_config_t; - -int sp804_init(sp804_t *sp804, ps_io_ops_t ops, sp804_config_t config); -/* convert between dmt ticks and ns */ -uint64_t sp804_ticks_to_ns(uint64_t ticks); -/* return true if an overflow irq is pending */ -bool sp804_is_irq_pending(sp804_t *sp804); -int sp804_set_timeout_ticks(sp804_t *timer, uint32_t ticks, bool periodic, bool irqs); -/* set a timeout in nano seconds */ -int sp804_set_timeout(sp804_t *timer, uint64_t ns, bool periodic, bool irqs); -int sp804_start(sp804_t *timer); -int sp804_stop(sp804_t *timer); -uint64_t sp804_get_time(sp804_t *timer); -uint64_t sp804_get_ticks(sp804_t *timer); -void sp804_destroy(sp804_t *sp804); diff --git a/libplatsupport/plat_include/hikey/platsupport/plat/dmt.h b/libplatsupport/plat_include/hikey/platsupport/plat/dmt.h deleted file mode 100644 index a661f3fc1..000000000 --- a/libplatsupport/plat_include/hikey/platsupport/plat/dmt.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) - * - * SPDX-License-Identifier: BSD-2-Clause - */ -#pragma once - -#include -#include - -#define DMT_PATH "/soc/timer@f8008000" - -#define DMT_REG_CHOICE 0 -#define DMT_IRQ_CHOICE 0 - -static UNUSED timer_properties_t dmtimer_props = { - .upcounter = false, - .timeouts = true, - .absolute_timeouts = false, - .relative_timeouts = true, - .periodic_timeouts = true, - .bit_width = 32, - .irqs = 1 -}; - -typedef volatile struct dmt_regs { - uint32_t load; - uint32_t value; - uint32_t control; - uint32_t intclr; - uint32_t ris; - uint32_t mis; - uint32_t bgload; -} dmt_regs_t; - -typedef struct { - /* set in init */ - ps_io_ops_t ops; - ltimer_callback_fn_t user_cb_fn; - void *user_cb_token; - ltimer_event_t user_cb_event; /* what are we being used for? */ - - /* set in fdt helper */ - volatile dmt_regs_t *dmt_map; - void *dmt_map_base; - pmem_region_t pmem; - irq_id_t irq_id; - - /* set in setup */ - uint32_t time_h; -} dmt_t; - -typedef struct { - const char *fdt_path; - ltimer_callback_fn_t user_cb_fn; - void *user_cb_token; - ltimer_event_t user_cb_event; -} dmt_config_t; - -int dmt_init(dmt_t *dmt, ps_io_ops_t ops, dmt_config_t config); -int dmt_init_secondary(dmt_t *dmt, dmt_t *dmtp, ps_io_ops_t ops, dmt_config_t config); -/* convert between dmt ticks and ns */ -uint64_t dmt_ticks_to_ns(uint64_t ticks); -/* return true if an overflow irq is pending */ -bool dmt_is_irq_pending(dmt_t *dmt); -int dmt_set_timeout_ticks(dmt_t *dmt, uint32_t ticks, bool periodic, bool irqs); -/* set a timeout in nano seconds */ -int dmt_set_timeout(dmt_t *dmt, uint64_t ns, bool periodic, bool irqs); -int dmt_start(dmt_t *dmt); -int dmt_stop(dmt_t *dmt); -uint64_t dmt_get_time(dmt_t *dmt); -uint64_t dmt_get_ticks(dmt_t *dmt); -void dmt_destroy(dmt_t *dmt); diff --git a/libplatsupport/src/driver/sp804/sp804_ltimer.c b/libplatsupport/src/driver/sp804/sp804_ltimer.c new file mode 100644 index 000000000..c5f16d723 --- /dev/null +++ b/libplatsupport/src/driver/sp804/sp804_ltimer.c @@ -0,0 +1,292 @@ +/* + * Copyright 2022, HENSOLDT Cyber GmbH + * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include "../../ltimer.h" + +/* + * A each sp804 timer peripheral has two timers. Timer #1 keeps track of the + * timeouts and timer #2 tracks the absolute time. It depends on the platform + * if there is one interrupt for both timers or separate interrupts. + */ + +typedef struct { + ps_io_ops_t ops; + ltimer_callback_fn_t callback_func; + void *callback_token; + uint32_t time_h; + pmem_region_t pmem; + void *mapping; + uint64_t freq_100KHz; + irq_id_t irq_id; + irq_id_t irq2_id; +} ltimer_sp804_t; + +static sp804_regs_t *ltimer_sp804_get_regs_timeout(ltimer_sp804_t *ltimer) +{ + return SP804_TIMER1_REGS(ltimer->mapping); +} + +static sp804_regs_t *ltimer_sp804_get_regs_timestamp(ltimer_sp804_t *ltimer) +{ + return SP804_TIMER2_REGS(ltimer->mapping); +} + +static uint64_t ltimer_sp804_get_time(ltimer_sp804_t *ltimer) +{ + sp804_regs_t *regs = ltimer_sp804_get_regs_timestamp(ltimer); + + /* sp804 is a down counter, invert the result */ + uint32_t high = ltimer->time_h; + uint32_t low = UINT32_MAX - sp804_get_ticks(regs); + + /* check after fetching low to see if we've missed a high bit */ + if (sp804_is_irq_pending(regs)) { + high += 1; + assert(high != 0); + } + + uint64_t ticks = ((uint64_t)high << 32) | low; + uint64_t ns = (ticks * 10000) / ltimer->freq_100KHz; + return ns; +} + +int ltimer_sp804_simple_set_timeout(void *data, uint64_t ns, timeout_type_t type) +{ + assert(data != NULL); + ltimer_sp804_t *ltimer = data; + + /* calculate relative timeout for absolute timeout */ + if (TIMEOUT_ABSOLUTE == type) { + uint64_t time_ns_now = ltimer_sp804_get_time(ltimer); + /* The timeout can't be in the past */ + if (time_ns_now > ns) { + return ETIME; + } + ns -= time_ns_now; + } + + uint64_t ticks64 = (ns * ltimer->freq_100KHz) / 10000; + /* There is no trivial way to set values greater 32-bit. */ + if (ticks64 > UINT32_MAX) { + ZF_LOGE("timout of %"PRIu64" ns (%"PRIu64" ticks) exceeds 32-bit range", + ns, ticks64); + return ETIME; + } + + /* The value 0 makes the interrupt trigger immediately. + * ToDo: might be better to reject a periodic timer with a timeout of 0. + */ + sp804_set_timeout(ltimer_sp804_get_regs_timeout(ltimer), + (uint32_t)ticks64, (type == TIMEOUT_PERIODIC), true); + return 0; +} + +static int ltimer_sp804_simple_get_time(void *data, uint64_t *time) +{ + assert(data != NULL); + assert(time != NULL); + ltimer_sp804_t *ltimer = data; + *time = ltimer_sp804_get_time(ltimer); + return 0; +} + +static int ltimer_sp804_simple_reset(void *data) +{ + ltimer_sp804_t *ltimer = data; + + sp804_regs_t *regs_timeout = ltimer_sp804_get_regs_timeout(ltimer); + sp804_reset(regs_timeout); + + sp804_regs_t *regs_timestamp = ltimer_sp804_get_regs_timestamp(ltimer); + sp804_reset(regs_timestamp); + sp804_start(regs_timestamp); + + return 0; +} + +static void ltimer_sp804_simple_destroy(void *data) +{ + int error; + + assert(data != NULL); + ltimer_sp804_t *ltimer = data; + + if (ltimer->irq_id != PS_INVALID_IRQ_ID) { + error = ps_irq_unregister(<imer->ops.irq_ops, ltimer->irq_id); + if (error) { + /* This is considered as a fatal error */ + ZF_LOGF("Failed to unregister IRQ (%d)", error); + UNREACHABLE(); + } + } + + if (ltimer->irq2_id != PS_INVALID_IRQ_ID) { + error = ps_irq_unregister(<imer->ops.irq_ops, ltimer->irq2_id); + if (error) { + /* This is considered as a fatal error */ + ZF_LOGF("Failed to unregister IRQ (%d)", error); + UNREACHABLE(); + } + } + + ps_pmem_unmap(<imer->ops, ltimer->pmem, (void *)ltimer->mapping); + + ps_free(<imer->ops.malloc_ops, sizeof(ltimer_sp804_t), ltimer); +} + +static void ltimer_sp804_handle_irq(void *data, ps_irq_acknowledge_fn_t ack_fn, + void *ack_data) +{ + assert(data != NULL); + ltimer_sp804_t *ltimer = data; + + sp804_regs_t *regs_timestamp = ltimer_sp804_get_regs_timestamp(ltimer); + bool is_overflow = false; + if (sp804_is_irq_pending(regs_timestamp)) { + is_overflow = true; + sp804_clear_intr(regs_timestamp); + ltimer->time_h++; + } + + sp804_regs_t *regs_timeout = ltimer_sp804_get_regs_timeout(ltimer); + bool is_timeout = false; + if (sp804_is_irq_pending(regs_timeout)) { + is_timeout = true; + sp804_clear_intr(regs_timeout); + } + + if (ack_fn) { + int error = ack_fn(ack_data); + if (error) { + /* This is considered as a fatal error */ + ZF_LOGF("Failed to acknowledge the timer's interrupts (%d)", error); + UNREACHABLE(); + } + } + + if (ltimer->callback_func) { + if (is_timeout) { + ltimer->callback_func(ltimer->callback_token, LTIMER_TIMEOUT_EVENT); + } + if (is_overflow) { + ltimer->callback_func(ltimer->callback_token, LTIMER_OVERFLOW_EVENT); + } + } +} + +int ltimer_sp804_init(ltimer_t *ltimer, const char *ftd_path, + uint64_t freq, ps_io_ops_t ops, + ltimer_callback_fn_t callback, void *callback_token) +{ + int error; + + if (ltimer == NULL) { + ZF_LOGE("ltimer cannot be NULL"); + return EINVAL; + } + + error = create_ltimer_simple(ltimer, ops, sizeof(ltimer_sp804_t), + ltimer_sp804_simple_get_time, + ltimer_sp804_simple_set_timeout, + ltimer_sp804_simple_reset, + ltimer_sp804_simple_destroy); + if (error) { + ZF_LOGE("Failed to create ltimer simple (%d)", error); + return error; + } + + ltimer_sp804_t *ltimer_sp804 = ltimer->data; + + ltimer_sp804->ops = ops; + ltimer_sp804->callback_func = callback, + ltimer_sp804->callback_token = callback_token, + + /* Assume the frequency is a multiple of 100 KHz so the numbers do not get + * too big during calculations and there are less rounding errors. + */ + assert(0 == (freq % 100000)); + ltimer_sp804->freq_100KHz = freq / 100000; + + /* Set up timer1 for timeouts and timer2 for timestamps. We can't use + * helper_fdt_alloc_simple() here, because there are platforms where the + * timers have independent interrupts. + * Set safe default to allow cleanup if init fails + */ + ltimer_sp804->mapping = NULL; + ltimer_sp804->irq_id = PS_INVALID_IRQ_ID; + ltimer_sp804->irq2_id = PS_INVALID_IRQ_ID; + + /* Gather FDT info */ + ps_fdt_cookie_t *cookie = NULL; + error = ps_fdt_read_path(&ops.io_fdt, &ops.malloc_ops, ftd_path, &cookie); + if (error) { + ZF_LOGE("failed to read path (%d, %s)", error, ftd_path); + goto init_cleanup; + } + + ltimer_sp804->mapping = ps_fdt_index_map_register(&ops, cookie, 0, + <imer_sp804->pmem); + if (ltimer_sp804->mapping == NULL) { + ZF_LOGE("failed to map registers"); + goto init_cleanup; + } + + ltimer_sp804->irq_id = ps_fdt_index_register_irq(&ops, cookie, 0, + ltimer_sp804_handle_irq, + ltimer_sp804); + if (ltimer_sp804->irq_id <= PS_INVALID_IRQ_ID) { + ZF_LOGE("failed to register irq (%d)", ltimer_sp804->irq_id); + goto init_cleanup; + } + +#ifdef SP804_LTIMER_MULTIPLE_INTR + ltimer_sp804->irq2_id = ps_fdt_index_register_irq(&ops, cookie, 1, + ltimer_sp804_handle_irq, + ltimer_sp804); + if (ltimer_sp804->irq2_id <= PS_INVALID_IRQ_ID) { + ZF_LOGE("failed to register irq 2 (%d)", ltimer_sp804->irq2_id); + goto init_cleanup; + } +#endif /* SP804_LTIMER_MULTIPLE_INTR */ + +init_cleanup: + if (cookie) { + int error2 = ps_fdt_cleanup_cookie(&ops.malloc_ops, cookie); + if (error2) { + ZF_LOGE("failed to clean up FTD cookie (%d)", error2); + error = error2; + } + } + if (error) { + ltimer_sp804_simple_destroy(ltimer_sp804); + ZF_LOGE("sp804 ltimer init failed (%d)", error); + return error; + } + + sp804_regs_t *regs_timeout = ltimer_sp804_get_regs_timeout(ltimer_sp804); + sp804_regs_t *regs_timestamp = ltimer_sp804_get_regs_timestamp(ltimer_sp804); + + sp804_reset(regs_timeout); + sp804_reset(regs_timestamp); + + /* Start the timestamp counter (setting a value implies starting). */ + sp804_set_timeout(regs_timestamp, UINT32_MAX, true, true); + + return 0; +} diff --git a/libplatsupport/src/plat/fvp/ltimer.c b/libplatsupport/src/plat/fvp/ltimer.c index 6ec6cdbac..afa534f5e 100644 --- a/libplatsupport/src/plat/fvp/ltimer.c +++ b/libplatsupport/src/plat/fvp/ltimer.c @@ -3,144 +3,25 @@ * * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include - -#include -#include +#include #include -#include -#include +#include #include "../../ltimer.h" -/* - * We use two sp804 timers: one to keep track of an absolute time, the other for timeouts. +/* FVP has two sp804 timers, each running at 35 MHz + * /soc/timer@1c110000 + * /soc/timer@1c120000 */ +#define SP804_LTIMER_PATH "/soc/timer@1c110000" +#define SP804_LTIMER_FREQ 35000000 -typedef struct { - /* fvp sp804 have 2 timers per frame, we just use one per each */ - sp804_t sp804_timeout; - sp804_t sp804_timestamp; - ps_io_ops_t ops; -} fvp_ltimer_t; - -static int get_time(void *data, uint64_t *time) -{ - fvp_ltimer_t *fvp_ltimer = data; - assert(data != NULL); - assert(time != NULL); - - *time = sp804_get_time(&fvp_ltimer->sp804_timestamp); - return 0; -} - -int set_timeout(void *data, uint64_t ns, timeout_type_t type) -{ - if (type == TIMEOUT_ABSOLUTE) { - uint64_t time; - int error = get_time(data, &time); - if (error) { - return error; - } - if (time > ns) { - return ETIME; - } - ns -= time; - } - - fvp_ltimer_t *fvp_ltimer = data; - return sp804_set_timeout(&fvp_ltimer->sp804_timeout, ns, type == TIMEOUT_PERIODIC, true); -} - -static int reset(void *data) -{ - fvp_ltimer_t *fvp_ltimer = data; - /* restart the rtc */ - sp804_stop(&fvp_ltimer->sp804_timeout); - sp804_start(&fvp_ltimer->sp804_timeout); - return 0; -} - -static void destroy(void *data) +int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, + ltimer_callback_fn_t callback, void *callback_token) { - assert(data != NULL); - fvp_ltimer_t *fvp_ltimer = data; - sp804_destroy(&fvp_ltimer->sp804_timeout); - sp804_destroy(&fvp_ltimer->sp804_timestamp); - ps_free(&fvp_ltimer->ops.malloc_ops, sizeof(fvp_ltimer_t), fvp_ltimer); -} - -int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_token) -{ - int error; - - if (ltimer == NULL) { - ZF_LOGE("ltimer cannot be NULL"); - return EINVAL; - } - - error = create_ltimer_simple( - ltimer, ops, sizeof(fvp_ltimer_t), - get_time, set_timeout, reset, destroy - ); - if (error) { - ZF_LOGE("Failed to create ltimer simple"); - return error; - } - - fvp_ltimer_t *fvp_ltimer = ltimer->data; - fvp_ltimer->ops = ops; - - /* set up an SP804 for timeouts */ - sp804_config_t sp804_config = { - .fdt_path = SP804_TIMER1_PATH, - .user_cb_fn = callback, - .user_cb_token = callback_token, - .user_cb_event = LTIMER_TIMEOUT_EVENT - }; - - error = sp804_init(&fvp_ltimer->sp804_timeout, ops, sp804_config); - if (error) { - ZF_LOGE("Failed to init timeout timer"); - destroy(&fvp_ltimer); - return error; - } - - error = sp804_start(&fvp_ltimer->sp804_timeout); - if (error) { - ZF_LOGE("Failed to start timeout timer"); - destroy(&fvp_ltimer); - return error; - } - - /* another for timestamps */ - sp804_config.fdt_path = SP804_TIMER2_PATH; - sp804_config.user_cb_event = LTIMER_OVERFLOW_EVENT; - - error = sp804_init(&fvp_ltimer->sp804_timestamp, ops, sp804_config); - if (error) { - ZF_LOGE("Failed to init timestamp timer"); - destroy(&fvp_ltimer); - return error; - } - - error = sp804_start(&fvp_ltimer->sp804_timestamp); - if (error) { - ZF_LOGE("Failed to start timestamp timer"); - destroy(&fvp_ltimer); - return error; - } - - error = sp804_set_timeout_ticks(&fvp_ltimer->sp804_timestamp, UINT32_MAX, true, true); - if (error) { - ZF_LOGE("Failed to set timeout ticks for timer"); - destroy(&fvp_ltimer); - return error; - } - - return 0; + return ltimer_sp804_init(ltimer, SP804_LTIMER_PATH, SP804_LTIMER_FREQ, ops, + callback, callback_token); } /* This function is intended to be deleted, diff --git a/libplatsupport/src/plat/fvp/sp804.c b/libplatsupport/src/plat/fvp/sp804.c deleted file mode 100644 index b2d860989..000000000 --- a/libplatsupport/src/plat/fvp/sp804.c +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230) - * - * SPDX-License-Identifier: BSD-2-Clause - */ -#include -#include -#include - -#include -#include - -#include - -#include "../../ltimer.h" - -/* This file is mostly the same as the dmt.c file for the hikey. - * Consider to merge the two files as a single driver file for - * SP804. - */ - -#define TCLR_ONESHOT BIT(0) -#define TCLR_VALUE_32 BIT(1) -#define TCLR_INTENABLE BIT(5) -#define TCLR_AUTORELOAD BIT(6) -#define TCLR_STARTTIMER BIT(7) -/* It looks like the FVP does not emulate time accurately. Thus, pick - * a small Hz that triggers interrupts in a reasonable time */ -#define TICKS_PER_SECOND 35000 -#define TICKS_PER_MS (TICKS_PER_SECOND / MS_IN_S) - -static void sp804_timer_reset(sp804_t *sp804) -{ - assert(sp804 != NULL && sp804->sp804_map != NULL); - sp804_regs_t *sp804_regs = sp804->sp804_map; - sp804_regs->control = 0; - - sp804->time_h = 0; -} - -int sp804_stop(sp804_t *sp804) -{ - if (sp804 == NULL) { - return EINVAL; - } - assert(sp804->sp804_map != NULL); - sp804_regs_t *sp804_regs = sp804->sp804_map; - sp804_regs->control = sp804_regs->control & ~TCLR_STARTTIMER; - return 0; -} - -int sp804_start(sp804_t *sp804) -{ - if (sp804 == NULL) { - return EINVAL; - } - assert(sp804->sp804_map != NULL); - sp804_regs_t *sp804_regs = sp804->sp804_map; - sp804_regs->control = sp804_regs->control | TCLR_STARTTIMER; - return 0; -} - -uint64_t sp804_ticks_to_ns(uint64_t ticks) -{ - return ticks / TICKS_PER_MS * NS_IN_MS; -} - -bool sp804_is_irq_pending(sp804_t *sp804) -{ - if (sp804) { - assert(sp804->sp804_map != NULL); - return !!sp804->sp804_map->ris; - } - return false; -} - -int sp804_set_timeout(sp804_t *sp804, uint64_t ns, bool periodic, bool irqs) -{ - uint64_t ticks64 = ns * TICKS_PER_MS / NS_IN_MS; - if (ticks64 > UINT32_MAX) { - return ETIME; - } - return sp804_set_timeout_ticks(sp804, ticks64, periodic, irqs); -} - -int sp804_set_timeout_ticks(sp804_t *sp804, uint32_t ticks, bool periodic, bool irqs) -{ - if (sp804 == NULL) { - return EINVAL; - } - int flags = periodic ? TCLR_AUTORELOAD : TCLR_ONESHOT; - flags |= irqs ? TCLR_INTENABLE : 0; - - assert(sp804->sp804_map != NULL); - sp804_regs_t *sp804_regs = sp804->sp804_map; - sp804_regs->control = 0; - - if (flags & TCLR_AUTORELOAD) { - sp804_regs->bgload = ticks; - } else { - sp804_regs->bgload = 0; - } - sp804_regs->load = ticks; - - /* The TIMERN_VALUE register is read-only. */ - sp804_regs->control = TCLR_STARTTIMER | TCLR_VALUE_32 - | flags; - - return 0; -} - -static void sp804_handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data) -{ - assert(data != NULL); - sp804_t *sp804 = data; - - if (sp804->user_cb_event == LTIMER_OVERFLOW_EVENT) { - sp804->time_h++; - } - - sp804_regs_t *sp804_regs = sp804->sp804_map; - sp804_regs->intclr = 0x1; - - ZF_LOGF_IF(acknowledge_fn(ack_data), "Failed to acknowledge the timer's interrupts"); - if (sp804->user_cb_fn) { - sp804->user_cb_fn(sp804->user_cb_token, sp804->user_cb_event); - } -} - -uint64_t sp804_get_ticks(sp804_t *sp804) -{ - assert(sp804 != NULL && sp804->sp804_map != NULL); - sp804_regs_t *sp804_regs = sp804->sp804_map; - return sp804_regs->value; -} - -uint64_t sp804_get_time(sp804_t *sp804) -{ - uint32_t high, low; - - /* timer must be being used for timekeeping */ - assert(sp804->user_cb_event == LTIMER_OVERFLOW_EVENT); - - /* sp804 is a down counter, invert the result */ - high = sp804->time_h; - low = UINT32_MAX - sp804_get_ticks(sp804); - - /* check after fetching low to see if we've missed a high bit */ - if (sp804_is_irq_pending(sp804)) { - high += 1; - assert(high != 0); - } - - uint64_t ticks = (((uint64_t) high << 32llu) | low); - return sp804_ticks_to_ns(ticks); -} - -void sp804_destroy(sp804_t *sp804) -{ - int error; - if (sp804->irq_id != PS_INVALID_IRQ_ID) { - error = ps_irq_unregister(&sp804->ops.irq_ops, sp804->irq_id); - ZF_LOGF_IF(error, "Failed to unregister IRQ"); - } - if (sp804->sp804_map != NULL) { - sp804_stop(sp804); - ps_pmem_unmap(&sp804->ops, sp804->pmem, (void *) sp804->sp804_map); - } -} - -int sp804_init(sp804_t *sp804, ps_io_ops_t ops, sp804_config_t config) -{ - int error; - - if (sp804 == NULL) { - ZF_LOGE("sp804 cannot be null"); - return EINVAL; - } - - sp804->ops = ops; - sp804->user_cb_fn = config.user_cb_fn; - sp804->user_cb_token = config.user_cb_token; - sp804->user_cb_event = config.user_cb_event; - - error = helper_fdt_alloc_simple( - &ops, config.fdt_path, - SP804_REG_CHOICE, SP804_IRQ_CHOICE, - (void *) &sp804->sp804_map, &sp804->pmem, &sp804->irq_id, - sp804_handle_irq, sp804 - ); - if (error) { - ZF_LOGE("Simple fdt alloc helper failed"); - return error; - } - - sp804_timer_reset(sp804); - return 0; -} diff --git a/libplatsupport/src/plat/hikey/dmt.c b/libplatsupport/src/plat/hikey/dmt.c deleted file mode 100644 index 26f83ca6a..000000000 --- a/libplatsupport/src/plat/hikey/dmt.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) - * - * SPDX-License-Identifier: BSD-2-Clause - */ -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include "../../ltimer.h" - -/* Driver for the HiSilison hi6220 hikey Dual-timer devices. - * - * There are 9 timer devices, each implementing two downcounters for a total - * of 18 downcounters. These downcounters run at 19.2MHz. - * - * The 9 timer devices each have their own physical frame address, but the - * 2 downcounters for each device reside in the same 4K frame. - * - * We have numbered the downcounters from 0-17 as distinct logical devices. - */ - -#define TCLR_ONESHOT BIT(0) -#define TCLR_VALUE_32 BIT(1) -#define TCLR_INTENABLE BIT(5) -#define TCLR_AUTORELOAD BIT(6) -#define TCLR_STARTTIMER BIT(7) -#define TICKS_PER_SECOND 19200000 -#define TICKS_PER_MS (TICKS_PER_SECOND / MS_IN_S) - -#define HIKEY_DUALTIMER_SECONDARY_TIMER_OFFSET (0x20) - -static void dmt_timer_reset(dmt_t *dmt) -{ - assert(dmt != NULL && dmt->dmt_map != NULL); - dmt_regs_t *dmt_regs = dmt->dmt_map; - dmt_regs->control = 0; - - dmt->time_h = 0; -} - -int dmt_stop(dmt_t *dmt) -{ - if (dmt == NULL) { - return EINVAL; - } - assert(dmt != NULL && dmt->dmt_map != NULL); - dmt_regs_t *dmt_regs = dmt->dmt_map; - dmt_regs->control = dmt_regs->control & ~TCLR_STARTTIMER; - return 0; -} - -int dmt_start(dmt_t *dmt) -{ - if (dmt == NULL) { - return EINVAL; - } - assert(dmt != NULL && dmt->dmt_map != NULL); - dmt_regs_t *dmt_regs = dmt->dmt_map; - dmt_regs->control = dmt_regs->control | TCLR_STARTTIMER; - return 0; -} - -uint64_t dmt_ticks_to_ns(uint64_t ticks) -{ - return ticks / TICKS_PER_MS * NS_IN_MS; -} - -bool dmt_is_irq_pending(dmt_t *dmt) -{ - if (dmt) { - assert(dmt != NULL && dmt->dmt_map != NULL); - return !!dmt->dmt_map->ris; - } - return false; -} - -int dmt_set_timeout(dmt_t *dmt, uint64_t ns, bool periodic, bool irqs) -{ - uint64_t ticks64 = ns * TICKS_PER_MS / NS_IN_MS; - if (ticks64 > UINT32_MAX) { - return ETIME; - } - return dmt_set_timeout_ticks(dmt, ticks64, periodic, irqs); -} - -int dmt_set_timeout_ticks(dmt_t *dmt, uint32_t ticks, bool periodic, bool irqs) -{ - if (dmt == NULL) { - return EINVAL; - } - assert(dmt != NULL && dmt->dmt_map != NULL); - - int flags = periodic ? TCLR_AUTORELOAD : TCLR_ONESHOT; - flags |= irqs ? TCLR_INTENABLE : 0; - - dmt_regs_t *dmt_regs = dmt->dmt_map; - dmt_regs->control = 0; - - /* No need to check for ticks == 0, because 0 is a valid value: - * - * Hikey Application Processor Function Description, section 2.3, "TIMERN_LOAD": - * "The minimum valid value of TIMERN_LOAD is 1. If 0 is written to TIMERN_LOAD, a - * timing interrupt is generated immediately." - * - * If the user supplies 0 as the argument, they'll just get an IRQ - * immediately. - */ - if (flags & TCLR_AUTORELOAD) { - /* Hikey Application Processor Function Description, section 2.3, "TIMERN_BGLOAD": - * "TIMERN_BGLOAD is an initial count value register in periodic mode. - * - * In periodic mode, when the value of TIMERN_BGLOAD is updated, the - * value of TIMERN_LOAD is changed to that of TIMERN_BGLOAD. However, - * the timer counter does not restart counting. After the counter - * decreases to 0, the value of TIMERN_LOAD (that is, - * the value of TIMERN_BGLOAD) is reloaded to the counter. - * dmt->regs->bgload = ticks; - * - * In other words, for periodic mode, load BGLOAD first, then write to - * LOAD. For oneshot mode, only write to LOAD. For good measure, write 0 - * to BGLOAD. - */ - dmt_regs->bgload = ticks; - } else { - dmt_regs->bgload = 0; - } - dmt_regs->load = ticks; - - /* The TIMERN_VALUE register is read-only. */ - dmt_regs->control = TCLR_STARTTIMER | TCLR_VALUE_32 - | flags; - - return 0; -} - -static void dmt_handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data) -{ - assert(data != NULL); - dmt_t *dmt = data; - - /* if we are being used for timestamps */ - if (dmt->user_cb_event == LTIMER_OVERFLOW_EVENT) { - dmt->time_h++; - } - - assert(dmt->dmt_map != NULL); - dmt_regs_t *dmt_regs = dmt->dmt_map; - dmt_regs->intclr = 0x1; - - ZF_LOGF_IF(acknowledge_fn(ack_data), "Failed to acknowledge the timer's interrupts"); - if (dmt->user_cb_fn) { - dmt->user_cb_fn(dmt->user_cb_token, dmt->user_cb_event); - } -} - -uint64_t dmt_get_ticks(dmt_t *dmt) -{ - assert(dmt != NULL && dmt->dmt_map != NULL); - dmt_regs_t *dmt_regs = dmt->dmt_map; - return dmt_regs->value; -} - -uint64_t dmt_get_time(dmt_t *dmt) -{ - uint32_t high, low; - - /* timer must be being used for timekeeping */ - assert(dmt->user_cb_event == LTIMER_OVERFLOW_EVENT); - - /* dmt is a down counter, invert the result */ - high = dmt->time_h; - low = UINT32_MAX - dmt_get_ticks(dmt); - - /* check after fetching low to see if we've missed a high bit */ - if (dmt_is_irq_pending(dmt)) { - high += 1; - assert(high != 0); - } - - uint64_t ticks = (((uint64_t) high << 32llu) | low); - return dmt_ticks_to_ns(ticks); -} - -void dmt_destroy(dmt_t *dmt) -{ - int error; - if (dmt->irq_id != PS_INVALID_IRQ_ID) { - error = ps_irq_unregister(&dmt->ops.irq_ops, dmt->irq_id); - ZF_LOGF_IF(error, "Failed to unregister IRQ"); - } - if (dmt->dmt_map != NULL) { - dmt_stop(dmt); - } - if (dmt->dmt_map_base != NULL) { - /* use base because dmt_map is adjusted based on whether secondary */ - ps_pmem_unmap(&dmt->ops, dmt->pmem, (void *) dmt->dmt_map_base); - } -} - -int dmt_init(dmt_t *dmt, ps_io_ops_t ops, dmt_config_t config) -{ - int error; - - if (dmt == NULL) { - ZF_LOGE("dmt cannot be null"); - return EINVAL; - } - - dmt->ops = ops; - dmt->user_cb_fn = config.user_cb_fn; - dmt->user_cb_token = config.user_cb_token; - dmt->user_cb_event = config.user_cb_event; - - error = helper_fdt_alloc_simple( - &ops, config.fdt_path, - DMT_REG_CHOICE, DMT_IRQ_CHOICE, - &dmt->dmt_map_base, &dmt->pmem, &dmt->irq_id, - dmt_handle_irq, dmt - ); - if (error) { - ZF_LOGE("Simple fdt alloc helper failed"); - return error; - } - - dmt->dmt_map = dmt->dmt_map_base; - - dmt_timer_reset(dmt); - return 0; -} - -/* initialise dmt using the base address of dmtp, so that we do not attempt to map - * that base address again but instead re-use it. */ -int dmt_init_secondary(dmt_t *dmt, dmt_t *dmtp, ps_io_ops_t ops, dmt_config_t config) -{ - int error; - - if (dmt == NULL || dmtp == NULL) { - ZF_LOGE("dmt or dmtp cannot be null"); - return EINVAL; - } - - dmt->ops = ops; - dmt->user_cb_fn = config.user_cb_fn; - dmt->user_cb_token = config.user_cb_token; - dmt->user_cb_event = config.user_cb_event; - - /* so that destroy does not try to unmap twice */ - dmt->dmt_map_base = NULL; - /* First sub-device is at offset 0, second sub-device is - * at offset 0x20 within the same page. */ - dmt->dmt_map = (void *)((uintptr_t) dmtp->dmt_map_base) + HIKEY_DUALTIMER_SECONDARY_TIMER_OFFSET; - dmt->irq_id = PS_INVALID_IRQ_ID; - - /* Gather FDT info */ - ps_fdt_cookie_t *cookie = NULL; - error = ps_fdt_read_path(&ops.io_fdt, &ops.malloc_ops, config.fdt_path, &cookie); - if (error) { - ZF_LOGE("Failed to read path (%d, %s)", error, config.fdt_path); - return error; - } - - /* choose irq 1 because secondary */ - irq_id_t irq_id = ps_fdt_index_register_irq(&ops, cookie, 1, dmt_handle_irq, dmt); - if (irq_id <= PS_INVALID_IRQ_ID) { - ZF_LOGE("Failed to register irqs (%d)", irq_id); - return irq_id; - } - - error = ps_fdt_cleanup_cookie(&ops.malloc_ops, cookie); - if (error) { - ZF_LOGE("Failed to clean up cookie (%d)", error); - return error; - } - - dmt->irq_id = irq_id; - - dmt_timer_reset(dmt); - return 0; -} diff --git a/libplatsupport/src/plat/hikey/ltimer.c b/libplatsupport/src/plat/hikey/ltimer.c index 355d2647e..4ad13acbc 100644 --- a/libplatsupport/src/plat/hikey/ltimer.c +++ b/libplatsupport/src/plat/hikey/ltimer.c @@ -3,147 +3,21 @@ * * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include - -#include -#include +#include #include -#include -#include -#include +#include #include "../../ltimer.h" -/* - * We use two dm timers: one to keep track of an absolute time, the other for timeouts. - */ -/* hikey dualtimers have 2 timers per frame, we just use one from each */ -typedef struct { - dmt_t dmt_timeout; - dmt_t dmt_timestamp; - ps_io_ops_t ops; -} hikey_ltimer_t; - -static int get_time(void *data, uint64_t *time) -{ - hikey_ltimer_t *hikey_ltimer = data; - assert(data != NULL); - assert(time != NULL); - - *time = dmt_get_time(&hikey_ltimer->dmt_timestamp); - return 0; -} - -int set_timeout(void *data, uint64_t ns, timeout_type_t type) -{ - if (type == TIMEOUT_ABSOLUTE) { - uint64_t time; - int error = get_time(data, &time); - if (error) { - return error; - } - if (time > ns) { - return ETIME; - } - ns -= time; - } - - hikey_ltimer_t *hikey_ltimer = data; - return dmt_set_timeout(&hikey_ltimer->dmt_timeout, ns, type == TIMEOUT_PERIODIC, true); -} - -static int reset(void *data) -{ - hikey_ltimer_t *hikey_ltimer = data; - /* restart the rtc */ - dmt_stop(&hikey_ltimer->dmt_timeout); - dmt_start(&hikey_ltimer->dmt_timeout); - return 0; -} - -static void destroy(void *data) -{ - assert(data != NULL); - hikey_ltimer_t *hikey_ltimer = data; - /* NOTE: Note that timeout is primary, timestamp is secondary. - * this means that timeout holds the region mapping. - * We must first destroy timestamp before destroying timeout - * so that the region is freed last. */ - dmt_destroy(&hikey_ltimer->dmt_timestamp); - dmt_destroy(&hikey_ltimer->dmt_timeout); - ps_free(&hikey_ltimer->ops.malloc_ops, sizeof(hikey_ltimer_t), hikey_ltimer); -} +#define SP804_LTIMER_PATH "/soc/timer@f8008000" +#define SP804_LTIMER_FREQ 19200000 -int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_token) +int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, + ltimer_callback_fn_t callback, void *callback_token) { - int error; - - if (ltimer == NULL) { - ZF_LOGE("ltimer cannot be NULL"); - return EINVAL; - } - - error = create_ltimer_simple( - ltimer, ops, sizeof(hikey_ltimer_t), - get_time, set_timeout, reset, destroy - ); - if (error) { - ZF_LOGE("Failed to create ltimer for hikey"); - return error; - } - - hikey_ltimer_t *hikey_ltimer = ltimer->data; - hikey_ltimer->ops = ops; - - /* set up a DMT for timeouts */ - dmt_config_t dmt_config = { - .fdt_path = DMT_PATH, - .user_cb_fn = callback, - .user_cb_token = callback_token, - .user_cb_event = LTIMER_TIMEOUT_EVENT - }; - - error = dmt_init(&hikey_ltimer->dmt_timeout, ops, dmt_config); - if (error) { - ZF_LOGE("Failed to init dmt timeout timer"); - destroy(hikey_ltimer); - return error; - } - - error = dmt_start(&hikey_ltimer->dmt_timeout); - if (error) { - ZF_LOGE("Failed to start dmt timeout timer"); - destroy(hikey_ltimer); - return error; - } - - /* set up a DMT for timestamps */ - dmt_config.user_cb_event = LTIMER_OVERFLOW_EVENT; - - error = dmt_init_secondary(&hikey_ltimer->dmt_timestamp, &hikey_ltimer->dmt_timeout, ops, dmt_config); - if (error) { - ZF_LOGE("Failed to init dmt secondary for timestamps"); - destroy(hikey_ltimer); - return error; - } - - error = dmt_start(&hikey_ltimer->dmt_timestamp); - if (error) { - ZF_LOGE("Failed to start dmt timestamp timer"); - destroy(hikey_ltimer); - return error; - } - - error = dmt_set_timeout_ticks(&hikey_ltimer->dmt_timestamp, UINT32_MAX, true, true); - if (error) { - ZF_LOGE("Failed to set ticks for dmt timestamp timer"); - destroy(hikey_ltimer); - return error; - } - - return 0; + return ltimer_sp804_init(ltimer, SP804_LTIMER_PATH, SP804_LTIMER_FREQ, ops, + callback, callback_token); } /* This function is intended to be deleted,