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