1/* 2 * Copyright 2019, Data61 3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO) 4 * ABN 41 687 119 230. 5 * 6 * This software may be distributed and modified according to the terms of 7 * the BSD 2-Clause license. Note that NO WARRANTY is provided. 8 * See "LICENSE_BSD2.txt" for details. 9 * 10 * @TAG(DATA61_BSD) 11 */ 12#include <stdio.h> 13#include <assert.h> 14#include <errno.h> 15 16#include <utils/util.h> 17#include <utils/time.h> 18 19#include <platsupport/plat/sp804.h> 20 21#include "../../ltimer.h" 22 23/* This file is mostly the same as the dmt.c file for the hikey. 24 * Consider to merge the two files as a single driver file for 25 * SP804. 26 */ 27 28#define TCLR_ONESHOT BIT(0) 29#define TCLR_VALUE_32 BIT(1) 30#define TCLR_INTENABLE BIT(5) 31#define TCLR_AUTORELOAD BIT(6) 32#define TCLR_STARTTIMER BIT(7) 33/* It looks like the FVP does not emulate time accruately. Thus, pick 34 * a small Hz that triggers interrupts in a reasonable time */ 35#define TICKS_PER_SECOND 35000 36#define TICKS_PER_MS (TICKS_PER_SECOND / MS_IN_S) 37 38static void sp804_timer_reset(sp804_t *sp804) 39{ 40 assert(sp804 != NULL && sp804->sp804_map != NULL); 41 sp804_regs_t *sp804_regs = sp804->sp804_map; 42 sp804_regs->control = 0; 43 44 sp804->time_h = 0; 45} 46 47int sp804_stop(sp804_t *sp804) 48{ 49 if (sp804 == NULL) { 50 return EINVAL; 51 } 52 assert(sp804->sp804_map != NULL); 53 sp804_regs_t *sp804_regs = sp804->sp804_map; 54 sp804_regs->control = sp804_regs->control & ~TCLR_STARTTIMER; 55 return 0; 56} 57 58int sp804_start(sp804_t *sp804) 59{ 60 if (sp804 == NULL) { 61 return EINVAL; 62 } 63 assert(sp804->sp804_map != NULL); 64 sp804_regs_t *sp804_regs = sp804->sp804_map; 65 sp804_regs->control = sp804_regs->control | TCLR_STARTTIMER; 66 return 0; 67} 68 69uint64_t sp804_ticks_to_ns(uint64_t ticks) 70{ 71 return ticks / TICKS_PER_MS * NS_IN_MS; 72} 73 74bool sp804_is_irq_pending(sp804_t *sp804) 75{ 76 if (sp804) { 77 assert(sp804->sp804_map != NULL); 78 return !!sp804->sp804_map->ris; 79 } 80 return false; 81} 82 83int sp804_set_timeout(sp804_t *sp804, uint64_t ns, bool periodic, bool irqs) 84{ 85 uint64_t ticks64 = ns * TICKS_PER_MS / NS_IN_MS; 86 if (ticks64 > UINT32_MAX) { 87 return ETIME; 88 } 89 return sp804_set_timeout_ticks(sp804, ticks64, periodic, irqs); 90} 91 92int sp804_set_timeout_ticks(sp804_t *sp804, uint32_t ticks, bool periodic, bool irqs) 93{ 94 if (sp804 == NULL) { 95 return EINVAL; 96 } 97 int flags = periodic ? TCLR_AUTORELOAD : TCLR_ONESHOT; 98 flags |= irqs ? TCLR_INTENABLE : 0; 99 100 assert(sp804->sp804_map != NULL); 101 sp804_regs_t *sp804_regs = sp804->sp804_map; 102 sp804_regs->control = 0; 103 104 if (flags & TCLR_AUTORELOAD) { 105 sp804_regs->bgload = ticks; 106 } else { 107 sp804_regs->bgload = 0; 108 } 109 sp804_regs->load = ticks; 110 111 /* The TIMERN_VALUE register is read-only. */ 112 sp804_regs->control = TCLR_STARTTIMER | TCLR_VALUE_32 113 | flags; 114 115 return 0; 116} 117 118static void sp804_handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data) 119{ 120 assert(data != NULL); 121 sp804_t *sp804 = data; 122 123 if (sp804->user_cb_event == LTIMER_OVERFLOW_EVENT) { 124 sp804->time_h++; 125 } 126 127 sp804_regs_t *sp804_regs = sp804->sp804_map; 128 sp804_regs->intclr = 0x1; 129 130 ZF_LOGF_IF(acknowledge_fn(ack_data), "Failed to acknowledge the timer's interrupts"); 131 if (sp804->user_cb_fn) { 132 sp804->user_cb_fn(sp804->user_cb_token, sp804->user_cb_event); 133 } 134} 135 136uint64_t sp804_get_ticks(sp804_t *sp804) 137{ 138 assert(sp804 != NULL && sp804->sp804_map != NULL); 139 sp804_regs_t *sp804_regs = sp804->sp804_map; 140 return sp804_regs->value; 141} 142 143uint64_t sp804_get_time(sp804_t *sp804) 144{ 145 uint32_t high, low; 146 147 /* timer must be being used for timekeeping */ 148 assert(sp804->user_cb_event == LTIMER_OVERFLOW_EVENT); 149 150 /* sp804 is a down counter, invert the result */ 151 high = sp804->time_h; 152 low = UINT32_MAX - sp804_get_ticks(sp804); 153 154 /* check after fetching low to see if we've missed a high bit */ 155 if (sp804_is_irq_pending(sp804)) { 156 high += 1; 157 assert(high != 0); 158 } 159 160 uint64_t ticks = (((uint64_t) high << 32llu) | low); 161 return sp804_ticks_to_ns(ticks); 162} 163 164void sp804_destroy(sp804_t *sp804) 165{ 166 int error; 167 if (sp804->irq_id != PS_INVALID_IRQ_ID) { 168 error = ps_irq_unregister(&sp804->ops.irq_ops, sp804->irq_id); 169 ZF_LOGF_IF(error, "Failed to unregister IRQ"); 170 } 171 if (sp804->sp804_map != NULL) { 172 sp804_stop(sp804); 173 ps_pmem_unmap(&sp804->ops, sp804->pmem, (void *) sp804->sp804_map); 174 } 175} 176 177int sp804_init(sp804_t *sp804, ps_io_ops_t ops, sp804_config_t config) 178{ 179 int error; 180 181 if (sp804 == NULL) { 182 ZF_LOGE("sp804 cannot be null"); 183 return EINVAL; 184 } 185 186 sp804->ops = ops; 187 sp804->user_cb_fn = config.user_cb_fn; 188 sp804->user_cb_token = config.user_cb_token; 189 sp804->user_cb_event = config.user_cb_event; 190 191 error = helper_fdt_alloc_simple( 192 &ops, config.fdt_path, 193 SP804_REG_CHOICE, SP804_IRQ_CHOICE, 194 (void *) &sp804->sp804_map, &sp804->pmem, &sp804->irq_id, 195 sp804_handle_irq, sp804 196 ); 197 if (error) { 198 ZF_LOGE("Simple fdt alloc helper failed"); 199 return error; 200 } 201 202 sp804_timer_reset(sp804); 203 return 0; 204} 205