/* * Copyright 2019, Data61 * Commonwealth Scientific and Industrial Research Organisation (CSIRO) * ABN 41 687 119 230. * * This software may be distributed and modified according to the terms of * the BSD 2-Clause license. Note that NO WARRANTY is provided. * See "LICENSE_BSD2.txt" for details. * * @TAG(DATA61_BSD) */ #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 accruately. 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; }