1/*
2 * Copyright 2019, Data61
3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO)
4 * ABN 41 687 128 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 <autoconf.h>
13#include <stdio.h>
14#include <assert.h>
15
16#include <utils/util.h>
17#include <utils/time.h>
18#include <utils/frequency.h>
19
20#include <platsupport/ltimer.h>
21#include <platsupport/arch/generic_timer.h>
22#include <platsupport/io.h>
23
24#include "../../ltimer.h"
25
26typedef struct {
27    uint32_t freq; // frequency of the generic timer
28    uint64_t period; // period of a current periodic timeout, in ns
29    irq_id_t timer_irq_id;
30    timer_callback_data_t callback_data;
31    ltimer_callback_fn_t user_callback;
32    void *user_callback_token;
33    ps_io_ops_t ops;
34} generic_ltimer_t;
35
36static size_t get_num_irqs(void *data)
37{
38    return 1;
39}
40
41static int get_nth_irq(void *data, size_t n, ps_irq_t *irq)
42{
43    assert(n < get_num_irqs(data));
44    irq->type = PS_PER_CPU;
45    irq->cpu.number = GENERIC_TIMER_PCNT_IRQ;
46    irq->cpu.trigger = 0;
47    irq->cpu.cpu_idx = 0;
48
49    return 0;
50}
51
52static size_t get_num_pmems(void *data)
53{
54    return 0;
55}
56
57static int get_nth_pmem(void *data, size_t n, pmem_region_t *region)
58{
59    return -1;
60}
61
62static int get_time(void *data, uint64_t *time)
63{
64    assert(data != NULL);
65    assert(time != NULL);
66
67    generic_ltimer_t *ltimer = data;
68    uint64_t ticks = generic_timer_get_ticks();
69    *time = freq_cycles_and_hz_to_ns(ticks, ltimer->freq);
70    return 0;
71}
72
73int set_timeout(void *data, uint64_t ns, timeout_type_t type)
74{
75    generic_ltimer_t *ltimer = data;
76    if (type == TIMEOUT_PERIODIC) {
77        ltimer->period = ns;
78    } else {
79        ltimer->period = 0;
80    }
81
82    uint64_t time;
83    int error = get_time(data, &time);
84    if (type != TIMEOUT_ABSOLUTE) {
85        if (error) {
86            return error;
87        }
88        ns += time;
89    }
90
91    if (time > ns) {
92        return ETIME;
93    }
94    generic_timer_set_compare(freq_ns_and_hz_to_cycles(ns, ltimer->freq));
95
96    return 0;
97}
98
99static int handle_irq(void *data, ps_irq_t *irq)
100{
101    if (irq->type != PS_PER_CPU &&
102        irq->cpu.number != GENERIC_TIMER_PCNT_IRQ &&
103        irq->cpu.trigger != 0 &&
104        irq->cpu.cpu_idx != 0) {
105        return EINVAL;
106    }
107
108    generic_ltimer_t *ltimer = data;
109    if (ltimer->period) {
110        set_timeout(data, ltimer->period, TIMEOUT_PERIODIC);
111    } else {
112        generic_timer_set_compare(UINT64_MAX);
113    }
114
115    /* Interrupts are only generated for the timeout portion */
116    if (ltimer->user_callback) {
117        ltimer->user_callback(ltimer->user_callback_token, LTIMER_TIMEOUT_EVENT);
118    }
119    return 0;
120}
121
122static int get_resolution(void *data, uint64_t *resolution)
123{
124    return ENOSYS;
125}
126
127static int reset(void *data)
128{
129    generic_ltimer_t *generic_ltimer = data;
130    generic_ltimer->period = 0;
131    generic_timer_set_compare(UINT64_MAX);
132    return 0;
133}
134
135static void destroy(void *data)
136{
137    int error;
138    generic_ltimer_t *generic_ltimer = data;
139    generic_timer_disable();
140    if (generic_ltimer->callback_data.irq) {
141        ps_free(&generic_ltimer->ops.malloc_ops, sizeof(ps_irq_t), generic_ltimer->callback_data.irq);
142    }
143    if (generic_ltimer->timer_irq_id > PS_INVALID_IRQ_ID) {
144        error = ps_irq_unregister(&generic_ltimer->ops.irq_ops, generic_ltimer->timer_irq_id);
145        ZF_LOGF_IF(error, "Failed to unregister IRQ ID");
146    }
147    ps_free(&generic_ltimer->ops.malloc_ops, sizeof(generic_ltimer_t), generic_ltimer);
148}
149
150int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_data)
151{
152    if (ltimer == NULL) {
153        ZF_LOGE("ltimer cannot be NULL");
154        return EINVAL;
155    }
156
157    if (!config_set(CONFIG_EXPORT_PCNT_USER)) {
158        ZF_LOGE("Generic timer not exported!");
159        return ENXIO;
160    }
161
162    ltimer_default_describe(ltimer, ops);
163    ltimer->get_time = get_time;
164    ltimer->get_resolution = get_resolution;
165    ltimer->set_timeout = set_timeout;
166    ltimer->reset = reset;
167    ltimer->destroy = destroy;
168
169    int error = ps_calloc(&ops.malloc_ops, 1, sizeof(generic_ltimer_t), &ltimer->data);
170    if (error) {
171        return error;
172    }
173    assert(ltimer->data != NULL);
174    generic_ltimer_t *generic_ltimer = ltimer->data;
175
176    generic_ltimer->ops = ops;
177    generic_ltimer->user_callback = callback;
178    generic_ltimer->user_callback_token = callback_data;
179
180    generic_ltimer->freq = generic_timer_get_freq();
181    if (generic_ltimer->freq == 0) {
182        ZF_LOGE("Couldn't read timer frequency");
183        return ENXIO;
184    }
185
186    generic_timer_set_compare(UINT64_MAX);
187    generic_timer_enable();
188
189    /* register the IRQ we need */
190    error = ps_calloc(&ops.malloc_ops, 1, sizeof(ps_irq_t), (void **) &generic_ltimer->callback_data.irq);
191    if (error) {
192        destroy(ltimer->data);
193        return error;
194    }
195    generic_ltimer->callback_data.ltimer = ltimer;
196    generic_ltimer->callback_data.irq_handler = handle_irq;
197    error = get_nth_irq(ltimer->data, 0, generic_ltimer->callback_data.irq);
198    if (error) {
199        destroy(ltimer->data);
200        return error;
201    }
202    generic_ltimer->timer_irq_id = ps_irq_register(&ops.irq_ops, *generic_ltimer->callback_data.irq,
203                                                   handle_irq_wrapper, &generic_ltimer->callback_data);
204    if (generic_ltimer->timer_irq_id < 0) {
205        destroy(ltimer->data);
206        return EIO;
207    }
208
209    return 0;
210}
211
212int ltimer_default_describe(ltimer_t *ltimer, ps_io_ops_t ops)
213{
214    if (ltimer == NULL) {
215        ZF_LOGE("Timer is NULL!");
216        return EINVAL;
217    }
218
219    ltimer->get_num_irqs = get_num_irqs;
220    ltimer->get_nth_irq = get_nth_irq;
221    ltimer->get_num_pmems = get_num_pmems;
222    ltimer->get_nth_pmem = get_nth_pmem;
223    return 0;
224}
225