1/* 2 * Copyright 2017, 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/fdt.h> 20#include <platsupport/io.h> 21#include <platsupport/plat/dmt.h> 22 23#include "../../ltimer.h" 24 25/* Driver for the HiSilison hi6220 hikey Dual-timer devices. 26 * 27 * There are 9 timer devices, each implementing two downcounters for a total 28 * of 18 downcounters. These downcounters run at 19.2MHz. 29 * 30 * The 9 timer devices each have their own physical frame address, but the 31 * 2 downcounters for each device reside in the same 4K frame. 32 * 33 * We have numbered the downcounters from 0-17 as distinct logical devices. 34 */ 35 36#define TCLR_ONESHOT BIT(0) 37#define TCLR_VALUE_32 BIT(1) 38#define TCLR_INTENABLE BIT(5) 39#define TCLR_AUTORELOAD BIT(6) 40#define TCLR_STARTTIMER BIT(7) 41#define TICKS_PER_SECOND 19200000 42#define TICKS_PER_MS (TICKS_PER_SECOND / MS_IN_S) 43 44#define HIKEY_DUALTIMER_SECONDARY_TIMER_OFFSET (0x20) 45 46static void dmt_timer_reset(dmt_t *dmt) 47{ 48 assert(dmt != NULL && dmt->dmt_map != NULL); 49 dmt_regs_t *dmt_regs = dmt->dmt_map; 50 dmt_regs->control = 0; 51 52 dmt->time_h = 0; 53} 54 55int dmt_stop(dmt_t *dmt) 56{ 57 if (dmt == NULL) { 58 return EINVAL; 59 } 60 assert(dmt != NULL && dmt->dmt_map != NULL); 61 dmt_regs_t *dmt_regs = dmt->dmt_map; 62 dmt_regs->control = dmt_regs->control & ~TCLR_STARTTIMER; 63 return 0; 64} 65 66int dmt_start(dmt_t *dmt) 67{ 68 if (dmt == NULL) { 69 return EINVAL; 70 } 71 assert(dmt != NULL && dmt->dmt_map != NULL); 72 dmt_regs_t *dmt_regs = dmt->dmt_map; 73 dmt_regs->control = dmt_regs->control | TCLR_STARTTIMER; 74 return 0; 75} 76 77uint64_t dmt_ticks_to_ns(uint64_t ticks) 78{ 79 return ticks / TICKS_PER_MS * NS_IN_MS; 80} 81 82bool dmt_is_irq_pending(dmt_t *dmt) 83{ 84 if (dmt) { 85 assert(dmt != NULL && dmt->dmt_map != NULL); 86 return !!dmt->dmt_map->ris; 87 } 88 return false; 89} 90 91int dmt_set_timeout(dmt_t *dmt, uint64_t ns, bool periodic, bool irqs) 92{ 93 uint64_t ticks64 = ns * TICKS_PER_MS / NS_IN_MS; 94 if (ticks64 > UINT32_MAX) { 95 return ETIME; 96 } 97 return dmt_set_timeout_ticks(dmt, ticks64, periodic, irqs); 98} 99 100int dmt_set_timeout_ticks(dmt_t *dmt, uint32_t ticks, bool periodic, bool irqs) 101{ 102 if (dmt == NULL) { 103 return EINVAL; 104 } 105 assert(dmt != NULL && dmt->dmt_map != NULL); 106 107 int flags = periodic ? TCLR_AUTORELOAD : TCLR_ONESHOT; 108 flags |= irqs ? TCLR_INTENABLE : 0; 109 110 dmt_regs_t *dmt_regs = dmt->dmt_map; 111 dmt_regs->control = 0; 112 113 /* No need to check for ticks == 0, because 0 is a valid value: 114 * 115 * Hikey Application Processor Function Description, section 2.3, "TIMERN_LOAD": 116 * "The minimum valid value of TIMERN_LOAD is 1. If 0 is written to TIMERN_LOAD, a 117 * timing interrupt is generated immediately." 118 * 119 * If the user supplies 0 as the argument, they'll just get an IRQ 120 * immediately. 121 */ 122 if (flags & TCLR_AUTORELOAD) { 123 /* Hikey Application Processor Function Description, section 2.3, "TIMERN_BGLOAD": 124 * "TIMERN_BGLOAD is an initial count value register in periodic mode. 125 * 126 * In periodic mode, when the value of TIMERN_BGLOAD is updated, the 127 * value of TIMERN_LOAD is changed to that of TIMERN_BGLOAD. However, 128 * the timer counter does not restart counting. After the counter 129 * decreases to 0, the value of TIMERN_LOAD (that is, 130 * the value of TIMERN_BGLOAD) is reloaded to the counter. 131 * dmt->regs->bgload = ticks; 132 * 133 * In other words, for periodic mode, load BGLOAD first, then write to 134 * LOAD. For oneshot mode, only write to LOAD. For good measure, write 0 135 * to BGLOAD. 136 */ 137 dmt_regs->bgload = ticks; 138 } else { 139 dmt_regs->bgload = 0; 140 } 141 dmt_regs->load = ticks; 142 143 /* The TIMERN_VALUE register is read-only. */ 144 dmt_regs->control = TCLR_STARTTIMER | TCLR_VALUE_32 145 | flags; 146 147 return 0; 148} 149 150static void dmt_handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data) 151{ 152 assert(data != NULL); 153 dmt_t *dmt = data; 154 155 /* if we are being used for timestamps */ 156 if (dmt->user_cb_event == LTIMER_OVERFLOW_EVENT) { 157 dmt->time_h++; 158 } 159 160 assert(dmt->dmt_map != NULL); 161 dmt_regs_t *dmt_regs = dmt->dmt_map; 162 dmt_regs->intclr = 0x1; 163 164 ZF_LOGF_IF(acknowledge_fn(ack_data), "Failed to acknowledge the timer's interrupts"); 165 if (dmt->user_cb_fn) { 166 dmt->user_cb_fn(dmt->user_cb_token, dmt->user_cb_event); 167 } 168} 169 170uint64_t dmt_get_ticks(dmt_t *dmt) 171{ 172 assert(dmt != NULL && dmt->dmt_map != NULL); 173 dmt_regs_t *dmt_regs = dmt->dmt_map; 174 return dmt_regs->value; 175} 176 177uint64_t dmt_get_time(dmt_t *dmt) 178{ 179 uint32_t high, low; 180 181 /* timer must be being used for timekeeping */ 182 assert(dmt->user_cb_event == LTIMER_OVERFLOW_EVENT); 183 184 /* dmt is a down counter, invert the result */ 185 high = dmt->time_h; 186 low = UINT32_MAX - dmt_get_ticks(dmt); 187 188 /* check after fetching low to see if we've missed a high bit */ 189 if (dmt_is_irq_pending(dmt)) { 190 high += 1; 191 assert(high != 0); 192 } 193 194 uint64_t ticks = (((uint64_t) high << 32llu) | low); 195 return dmt_ticks_to_ns(ticks); 196} 197 198void dmt_destroy(dmt_t *dmt) 199{ 200 int error; 201 if (dmt->irq_id != PS_INVALID_IRQ_ID) { 202 error = ps_irq_unregister(&dmt->ops.irq_ops, dmt->irq_id); 203 ZF_LOGF_IF(error, "Failed to unregister IRQ"); 204 } 205 if (dmt->dmt_map != NULL) { 206 dmt_stop(dmt); 207 } 208 if (dmt->dmt_map_base != NULL) { 209 /* use base because dmt_map is adjusted based on whether secondary */ 210 ps_pmem_unmap(&dmt->ops, dmt->pmem, (void *) dmt->dmt_map_base); 211 } 212} 213 214int dmt_init(dmt_t *dmt, ps_io_ops_t ops, dmt_config_t config) 215{ 216 int error; 217 218 if (dmt == NULL) { 219 ZF_LOGE("dmt cannot be null"); 220 return EINVAL; 221 } 222 223 dmt->ops = ops; 224 dmt->user_cb_fn = config.user_cb_fn; 225 dmt->user_cb_token = config.user_cb_token; 226 dmt->user_cb_event = config.user_cb_event; 227 228 error = helper_fdt_alloc_simple( 229 &ops, config.fdt_path, 230 DMT_REG_CHOICE, DMT_IRQ_CHOICE, 231 &dmt->dmt_map_base, &dmt->pmem, &dmt->irq_id, 232 dmt_handle_irq, dmt 233 ); 234 if (error) { 235 ZF_LOGE("Simple fdt alloc helper failed"); 236 return error; 237 } 238 239 dmt->dmt_map = dmt->dmt_map_base; 240 241 dmt_timer_reset(dmt); 242 return 0; 243} 244 245/* initialise dmt using the base address of dmtp, so that we do not attempt to map 246 * that base address again but instead re-use it. */ 247int dmt_init_secondary(dmt_t *dmt, dmt_t *dmtp, ps_io_ops_t ops, dmt_config_t config) 248{ 249 int error; 250 251 if (dmt == NULL || dmtp == NULL) { 252 ZF_LOGE("dmt or dmtp cannot be null"); 253 return EINVAL; 254 } 255 256 dmt->ops = ops; 257 dmt->user_cb_fn = config.user_cb_fn; 258 dmt->user_cb_token = config.user_cb_token; 259 dmt->user_cb_event = config.user_cb_event; 260 261 /* so that destroy does not try to unmap twice */ 262 dmt->dmt_map_base = NULL; 263 /* First sub-device is at offset 0, second sub-device is 264 * at offset 0x20 within the same page. */ 265 dmt->dmt_map = (void *)((uintptr_t) dmtp->dmt_map_base) + HIKEY_DUALTIMER_SECONDARY_TIMER_OFFSET; 266 dmt->irq_id = PS_INVALID_IRQ_ID; 267 268 /* Gather FDT info */ 269 ps_fdt_cookie_t *cookie = NULL; 270 error = ps_fdt_read_path(&ops.io_fdt, &ops.malloc_ops, config.fdt_path, &cookie); 271 if (error) { 272 ZF_LOGE("Failed to read path (%d, %s)", error, config.fdt_path); 273 return error; 274 } 275 276 /* choose irq 1 because secondary */ 277 irq_id_t irq_id = ps_fdt_index_register_irq(&ops, cookie, 1, dmt_handle_irq, dmt); 278 if (irq_id <= PS_INVALID_IRQ_ID) { 279 ZF_LOGE("Failed to register irqs (%d)", irq_id); 280 return irq_id; 281 } 282 283 error = ps_fdt_cleanup_cookie(&ops.malloc_ops, cookie); 284 if (error) { 285 ZF_LOGE("Failed to clean up cookie (%d)", error); 286 return error; 287 } 288 289 dmt->irq_id = irq_id; 290 291 dmt_timer_reset(dmt); 292 return 0; 293} 294