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 13#include <stdio.h> 14#include <assert.h> 15#include <errno.h> 16 17#include <utils/util.h> 18 19#include <platsupport/timer.h> 20#include <platsupport/plat/timer.h> 21 22#include "../../ltimer.h" 23 24#define TIOCP_CFG_SOFTRESET BIT(0) 25 26#define TIER_MATCHENABLE BIT(0) 27#define TIER_OVERFLOWENABLE BIT(1) 28#define TIER_COMPAREENABLE BIT(2) 29 30#define TCLR_STARTTIMER BIT(0) 31#define TCLR_AUTORELOAD BIT(1) 32#define TCLR_PRESCALER BIT(5) 33#define TCLR_COMPAREENABLE BIT(6) 34 35#define TISR_MAT_IT_FLAG BIT(0) 36#define TISR_OVF_IT_FLAG BIT(1) 37#define TISR_TCAR_IT_FLAG BIT(2) 38 39#define TISR_IRQ_CLEAR (TISR_TCAR_IT_FLAG | TISR_OVF_IT_FLAG | TISR_MAT_IT_FLAG) 40 41static void dmt_reset(dmt_t *dmt) 42{ 43 /* stop */ 44 dmt->hw->tclr = 0; 45 dmt->hw->cfg = TIOCP_CFG_SOFTRESET; 46 while (dmt->hw->cfg & TIOCP_CFG_SOFTRESET); 47 dmt->hw->tier = TIER_OVERFLOWENABLE; 48 49 /* reset timekeeping */ 50 dmt->time_h = 0; 51} 52 53int dmt_stop(dmt_t *dmt) 54{ 55 if (dmt == NULL) { 56 return EINVAL; 57 } 58 59 dmt->hw->tclr = dmt->hw->tclr & ~TCLR_STARTTIMER; 60 return 0; 61} 62 63int dmt_start(dmt_t *dmt) 64{ 65 if (dmt == NULL) { 66 return EINVAL; 67 } 68 dmt->hw->tclr = dmt->hw->tclr | TCLR_STARTTIMER; 69 return 0; 70} 71 72int dmt_set_timeout(dmt_t *dmt, uint64_t ns, bool periodic) 73{ 74 if (dmt == NULL) { 75 return EINVAL; 76 } 77 dmt->hw->tclr = 0; /* stop */ 78 79 /* XXX handle prescaler */ 80 uint32_t tclrFlags = periodic ? TCLR_AUTORELOAD : 0; 81 82 uint64_t ticks = freq_ns_and_hz_to_cycles(ns, 24000000llu); 83 if (ticks < 2) { 84 return ETIME; 85 } 86 /* TODO: add functionality for 64 bit timeouts 87 */ 88 if (ticks > UINT32_MAX) { 89 ZF_LOGE("Timeout too far in future"); 90 return ETIME; 91 } 92 93 /* reload value */ 94 dmt->hw->tldr = 0xffffffff - (ticks); 95 96 /* counter */ 97 dmt->hw->tcrr = 0xffffffff - (ticks); 98 99 /* ack any pending irqs */ 100 dmt->hw->tisr = TISR_IRQ_CLEAR; 101 dmt->hw->tclr = TCLR_STARTTIMER | tclrFlags; 102 return 0; 103} 104 105int dmt_start_ticking_timer(dmt_t *dmt) 106{ 107 if (dmt == NULL) { 108 return EINVAL; 109 } 110 /* stop */ 111 dmt->hw->tclr = 0; 112 113 /* reset */ 114 dmt->hw->cfg = TIOCP_CFG_SOFTRESET; 115 while (dmt->hw->cfg & TIOCP_CFG_SOFTRESET); 116 117 /* reload value */ 118 dmt->hw->tldr = 0x0; 119 120 /* use overflow mode */ 121 dmt->hw->tier = TIER_OVERFLOWENABLE; 122 123 /* counter */ 124 dmt->hw->tcrr = 0x0; 125 126 /* ack any pending irqs */ 127 dmt->hw->tisr = TISR_IRQ_CLEAR; 128 129 /* start with auto reload */ 130 dmt->hw->tclr = TCLR_STARTTIMER | TCLR_AUTORELOAD; 131 return 0; 132} 133 134void dmt_handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data) 135{ 136 assert(data != NULL); 137 dmt_t *dmt = data; 138 139 /* ack any pending irqs */ 140 dmt->hw->tisr = TISR_IRQ_CLEAR; 141 142 /* if timer is being used for timekeeping, track overflows */ 143 if (dmt->user_cb_event == LTIMER_OVERFLOW_EVENT) { 144 dmt->time_h++; 145 } 146 147 ZF_LOGF_IF(acknowledge_fn(ack_data), "Failed to acknowledge the timer's interrupts"); 148 if (dmt->user_cb_fn) { 149 dmt->user_cb_fn(dmt->user_cb_token, dmt->user_cb_event); 150 } 151} 152 153bool dmt_pending_overflow(dmt_t *dmt) 154{ 155 return dmt->hw->tisr & TISR_OVF_IT_FLAG; 156} 157 158uint64_t dmt_get_time(dmt_t *dmt) 159{ 160 uint32_t high, low; 161 162 /* should be a timer being used for timekeeping */ 163 assert(dmt->user_cb_event == LTIMER_OVERFLOW_EVENT); 164 165 high = dmt->time_h; 166 low = dmt->hw->tcrr; 167 168 /* check after fetching low to see if we've missed a high bit */ 169 if (dmt_pending_overflow(dmt)) { 170 high += 1; 171 assert(high != 0); 172 } 173 174 uint64_t ticks = (((uint64_t)high << 32llu) | low); 175 return freq_cycles_and_hz_to_ns(ticks, 24000000llu); 176} 177 178void dmt_destroy(dmt_t *dmt) 179{ 180 int error; 181 if (dmt->irq_id != PS_INVALID_IRQ_ID) { 182 error = ps_irq_unregister(&dmt->ops.irq_ops, dmt->irq_id); 183 ZF_LOGF_IF(error, "Failed to unregister IRQ"); 184 } 185 if (dmt->hw != NULL) { 186 dmt_stop(dmt); 187 ps_pmem_unmap(&dmt->ops, dmt->pmem, (void *) dmt->hw); 188 } 189} 190 191int dmt_init(dmt_t *dmt, ps_io_ops_t ops, dmt_config_t config) 192{ 193 int error; 194 195 if (dmt == NULL) { 196 ZF_LOGE("dmt cannot be null"); 197 return EINVAL; 198 } 199 200 dmt->ops = ops; 201 dmt->user_cb_fn = config.user_cb_fn; 202 dmt->user_cb_token = config.user_cb_token; 203 dmt->user_cb_event = config.user_cb_event; 204 205 error = helper_fdt_alloc_simple( 206 &ops, config.fdt_path, 207 DMT_REG_CHOICE, DMT_IRQ_CHOICE, 208 (void *) &dmt->hw, &dmt->pmem, &dmt->irq_id, 209 dmt_handle_irq, dmt 210 ); 211 if (error) { 212 ZF_LOGE("Failed fdt simple alloc helper"); 213 dmt_destroy(dmt); 214 return error; 215 } 216 217 dmt_reset(dmt); 218 return 0; 219} 220