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 <platsupport/ltimer.h>
14#include <platsupport/io.h>
15#include <utils/io.h>
16#include <platsupport/plat/meson_timer.h>
17
18#include "../../ltimer.h"
19
20enum {
21    MESON_TIMER = 0,
22    NUM_TIMERS = 1
23};
24
25typedef struct {
26    meson_timer_t meson_timer;
27    void *meson_timer_vaddr;
28    irq_id_t timer_irq_id;
29    timer_callback_data_t callback_data;
30    ltimer_callback_fn_t user_callback;
31    void *user_callback_token;
32    ps_io_ops_t ops;
33} odroidc2_ltimer_t;
34
35static pmem_region_t pmems[] = {
36    {
37        .type = PMEM_TYPE_DEVICE,
38        .base_addr = TIMER_MAP_BASE,
39        .length = PAGE_SIZE_4K
40    }
41};
42
43static ps_irq_t irqs[] = {
44    {
45        .type = PS_TRIGGER,
46        .trigger.number = TIMER_A_IRQ,
47        .trigger.trigger = 1 /* edge-triggered */
48    }
49};
50
51static size_t get_num_irqs(void *data)
52{
53    return sizeof(irqs) / sizeof(irqs[0]);
54}
55
56static int get_nth_irq(void *data, size_t n, ps_irq_t *irq)
57{
58    assert(n < get_num_irqs(data));
59    *irq = irqs[n];
60    return 0;
61}
62
63static size_t get_num_pmems(void *data)
64{
65    return sizeof(pmems) / sizeof(pmems[0]);
66}
67
68static int get_nth_pmem(void *data, size_t n, pmem_region_t *region)
69{
70    assert(n < get_num_pmems(data));
71    *region = pmems[n];
72    return 0;
73}
74
75static int handle_irq(void *data, ps_irq_t *irq)
76{
77    assert(data != NULL);
78    odroidc2_ltimer_t *odroidc2_timer = data;
79
80    if (irq->irq.number != TIMER_A_IRQ) {
81        ZF_LOGE("Got IRQ from unkown source?");
82        return EINVAL;
83    }
84
85    if (odroidc2_timer->user_callback) {
86        /* The only interrupt event that we'll get is a timeout event */
87        odroidc2_timer->user_callback(odroidc2_timer->user_callback_token, LTIMER_TIMEOUT_EVENT);
88    }
89
90    return 0;
91}
92
93static int get_time(void *data, uint64_t *time)
94{
95    assert(time);
96    assert(data);
97
98    odroidc2_ltimer_t *odroidc2_timer = data;
99
100    *time = meson_get_time(&odroidc2_timer->meson_timer);
101    return 0;
102}
103
104static int get_resolution(void *data, uint64_t *resolution)
105{
106    return ENOSYS;
107}
108
109static int set_timeout(void *data, uint64_t ns, timeout_type_t type)
110{
111    assert(data != NULL);
112    odroidc2_ltimer_t *odroidc2_timer = data;
113    bool periodic = false;
114    uint16_t timeout;
115    uint64_t current_time;
116
117    switch (type) {
118    case TIMEOUT_ABSOLUTE:
119        current_time = meson_get_time(&odroidc2_timer->meson_timer);
120        if (current_time > ns) {
121            ZF_LOGE("Timeout in the past: now %lu, timeout %lu", current_time, ns);
122            return ETIME;
123        } else if ((ns - current_time) / NS_IN_MS > UINT16_MAX) {
124            ZF_LOGE("Timeout too far in the future");
125            return ETIME;
126        }
127
128        timeout = (ns - current_time) / NS_IN_MS;
129        break;
130    case TIMEOUT_PERIODIC:
131        periodic = true;
132    /* Fall through */
133    case TIMEOUT_RELATIVE:
134        if (ns / NS_IN_MS > UINT16_MAX) {
135            ZF_LOGE("Timeout too far in the future");
136            return ETIME;
137        }
138
139        timeout = ns / NS_IN_MS;
140        break;
141    }
142
143    /* The timer tick includes 0, i.e. one unit of time greater than the value written */
144    timeout = MIN(timeout, (uint16_t)(timeout - 1));
145    meson_set_timeout(&odroidc2_timer->meson_timer, timeout, periodic);
146    return 0;
147}
148
149static int reset(void *data)
150{
151    assert(data != NULL);
152    odroidc2_ltimer_t *odroidc2_timer = data;
153
154    meson_stop_timer(&odroidc2_timer->meson_timer);
155    return 0;
156}
157
158static void destroy(void *data)
159{
160    assert(data);
161    odroidc2_ltimer_t *odroidc2_timer = data;
162
163    meson_stop_timer(&odroidc2_timer->meson_timer);
164
165    if (odroidc2_timer->meson_timer_vaddr) {
166        ps_pmem_unmap(&odroidc2_timer->ops, pmems[0], odroidc2_timer->meson_timer_vaddr);
167    }
168
169    if (odroidc2_timer->timer_irq_id > PS_INVALID_IRQ_ID) {
170        int error = ps_irq_unregister(&odroidc2_timer->ops.irq_ops, odroidc2_timer->timer_irq_id);
171        ZF_LOGF_IF(error, "Failed to unregister IRQ");
172    }
173
174    ps_free(&odroidc2_timer->ops.malloc_ops, sizeof(odroidc2_ltimer_t), odroidc2_timer);
175    return;
176}
177
178int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_token)
179{
180    if (ltimer == NULL) {
181        ZF_LOGE("ltimer cannot be NULL");
182        return EINVAL;
183    }
184
185    int error = ltimer_default_describe(ltimer, ops);
186    if (error) {
187        return error;
188    }
189
190    ltimer->get_time = get_time;
191    ltimer->get_resolution = get_resolution;
192    ltimer->set_timeout = set_timeout;
193    ltimer->reset = reset;
194    ltimer->destroy = destroy;
195
196    error = ps_calloc(&ops.malloc_ops, 1, sizeof(odroidc2_ltimer_t), &ltimer->data);
197    if (error) {
198        return error;
199    }
200    assert(ltimer->data != NULL);
201
202    odroidc2_ltimer_t *odroidc2_timer = ltimer->data;
203
204    odroidc2_timer->ops = ops;
205    odroidc2_timer->user_callback = callback;
206    odroidc2_timer->user_callback_token = callback_token;
207    odroidc2_timer->timer_irq_id = PS_INVALID_IRQ_ID;
208
209    odroidc2_timer->meson_timer_vaddr = ps_pmem_map(&ops, pmems[0], false, PS_MEM_NORMAL);
210    if (odroidc2_timer->meson_timer_vaddr == NULL) {
211        destroy(ltimer->data);
212        return EINVAL;
213    }
214
215    odroidc2_timer->callback_data.ltimer = ltimer;
216    odroidc2_timer->callback_data.irq_handler = handle_irq;
217    odroidc2_timer->callback_data.irq = &irqs[0];
218
219    odroidc2_timer->timer_irq_id = ps_irq_register(&ops.irq_ops, irqs[0], handle_irq_wrapper,
220                                                   &odroidc2_timer->callback_data);
221    if (odroidc2_timer->timer_irq_id < 0) {
222        destroy(ltimer->data);
223        return EIO;
224    }
225
226    meson_timer_config_t meson_config = {
227        .vaddr = odroidc2_timer->meson_timer_vaddr
228    };
229    meson_init(&odroidc2_timer->meson_timer, meson_config);
230    return 0;
231}
232
233int ltimer_default_describe(ltimer_t *ltimer, ps_io_ops_t ops)
234{
235    if (ltimer == NULL) {
236        ZF_LOGE("Timer is NULL!");
237        return EINVAL;
238    }
239
240    *ltimer = (ltimer_t) {
241        .get_num_irqs = get_num_irqs,
242        .get_nth_irq = get_nth_irq,
243        .get_num_pmems = get_num_pmems,
244        .get_nth_pmem = get_nth_pmem
245    };
246
247    return 0;
248}
249