| /* |
| * Copyright 2017, 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 <stdio.h> |
| #include <assert.h> |
| #include <errno.h> |
| |
| #include <utils/util.h> |
| #include <utils/time.h> |
| |
| #include <platsupport/fdt.h> |
| #include <platsupport/io.h> |
| #include <platsupport/plat/dmt.h> |
| |
| #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; |
| } |