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/* Implementation of a logical timer for HiFive Unleashed platform.
13 *
14 * We use two pwms: one for the time and the other for timeouts.
15 */
16#include <platsupport/timer.h>
17#include <platsupport/ltimer.h>
18#include <platsupport/plat/pwm.h>
19#include <platsupport/pmem.h>
20#include <utils/util.h>
21
22#include "../../ltimer.h"
23
24enum {
25    COUNTER_TIMER,
26    TIMEOUT_TIMER,
27    NUM_TIMERS
28};
29
30typedef struct {
31    pwm_t pwm;
32    void *vaddr;
33} pwm_ltimer_t;
34
35typedef struct {
36    pwm_ltimer_t pwm_ltimers[NUM_TIMERS];
37    irq_id_t timer_irq_ids[NUM_TIMERS];
38    timer_callback_data_t callback_datas[NUM_TIMERS];
39    ltimer_callback_fn_t user_callback;
40    void *user_callback_token;
41    ps_io_ops_t ops;
42} hifive_timers_t;
43
44static ps_irq_t irqs[] = {
45    {
46        .type = PS_INTERRUPT,
47        .irq.number = PWM0_INTERRUPT0
48
49    },
50    {
51        .type = PS_INTERRUPT,
52        .irq.number = PWM1_INTERRUPT0
53    },
54};
55
56static pmem_region_t pmems[] = {
57    {
58        .type = PMEM_TYPE_DEVICE,
59        .base_addr = PWM0_PADDR,
60        .length = PAGE_SIZE_4K
61    },
62    {
63        .type = PMEM_TYPE_DEVICE,
64        .base_addr = PWM1_PADDR,
65        .length = PAGE_SIZE_4K
66    }
67};
68
69#define N_IRQS ARRAY_SIZE(irqs)
70#define N_PMEMS ARRAY_SIZE(pmems)
71
72 size_t get_num_irqs(void *data)
73{
74    return N_IRQS;
75}
76
77static int get_nth_irq(void *data, size_t n, ps_irq_t *irq)
78{
79    assert(n < N_IRQS);
80
81    *irq = irqs[n];
82    return 0;
83}
84
85static size_t get_num_pmems(void *data)
86{
87    return N_PMEMS;
88}
89
90static int get_nth_pmem(void *data, size_t n, pmem_region_t *paddr)
91{
92    assert(n < N_PMEMS);
93    *paddr = pmems[n];
94    return 0;
95}
96
97static int ltimer_handle_irq(void *data, ps_irq_t *irq)
98{
99    assert(data != NULL);
100    hifive_timers_t *timers = data;
101    long irq_number = irq->irq.number;
102    ltimer_event_t event;
103    if (irq_number == PWM0_INTERRUPT0) {
104        pwm_handle_irq(&timers->pwm_ltimers[COUNTER_TIMER].pwm, irq->irq.number);
105        event = LTIMER_OVERFLOW_EVENT;
106    } else if (irq_number == PWM1_INTERRUPT0) {
107        pwm_handle_irq(&timers->pwm_ltimers[TIMEOUT_TIMER].pwm, irq->irq.number);
108        event = LTIMER_TIMEOUT_EVENT;
109    } else {
110        ZF_LOGE("Invalid IRQ number: %d received.\n", irq_number);
111    }
112
113    if (timers->user_callback) {
114        timers->user_callback(timers->user_callback_token, event);
115    }
116
117    return 0;
118}
119
120static int get_time(void *data, uint64_t *time)
121{
122    assert(data != NULL);
123    assert(time != NULL);
124    hifive_timers_t *timers = data;
125
126    *time = pwm_get_time(&timers->pwm_ltimers[COUNTER_TIMER].pwm);
127    return 0;
128}
129
130static int get_resolution(void *data, uint64_t *resolution)
131{
132    return ENOSYS;
133}
134
135static int set_timeout(void *data, uint64_t ns, timeout_type_t type)
136{
137    assert(data != NULL);
138    hifive_timers_t *timers = data;
139
140    switch (type) {
141    case TIMEOUT_ABSOLUTE: {
142        uint64_t time = pwm_get_time(&timers->pwm_ltimers[COUNTER_TIMER].pwm);
143        if (time >= ns) {
144            return ETIME;
145        }
146        return pwm_set_timeout(&timers->pwm_ltimers[TIMEOUT_TIMER].pwm, ns - time, false);
147    }
148    case TIMEOUT_RELATIVE:
149        return pwm_set_timeout(&timers->pwm_ltimers[TIMEOUT_TIMER].pwm, ns, false);
150    case TIMEOUT_PERIODIC:
151        return pwm_set_timeout(&timers->pwm_ltimers[TIMEOUT_TIMER].pwm, ns, true);
152    }
153
154    return EINVAL;
155}
156
157static int reset(void *data)
158{
159    assert(data != NULL);
160    hifive_timers_t *timers = data;
161    pwm_stop(&timers->pwm_ltimers[COUNTER_TIMER].pwm);
162    pwm_start(&timers->pwm_ltimers[COUNTER_TIMER].pwm);
163    pwm_stop(&timers->pwm_ltimers[TIMEOUT_TIMER].pwm);
164    pwm_start(&timers->pwm_ltimers[TIMEOUT_TIMER].pwm);
165    return 0;
166}
167
168static void destroy(void *data)
169{
170    assert(data);
171    hifive_timers_t *timers = data;
172    for (int i = 0; i < NUM_TIMERS; i++) {
173        if (timers->pwm_ltimers[i].vaddr) {
174            pwm_stop(&timers->pwm_ltimers[i].pwm);
175            ps_pmem_unmap(&timers->ops, pmems[i], timers->pwm_ltimers[i].vaddr);
176        }
177        if (timers->timer_irq_ids[i] > PS_INVALID_IRQ_ID) {
178            int error = ps_irq_unregister(&timers->ops.irq_ops, timers->timer_irq_ids[i]);
179            ZF_LOGF_IF(error, "Failed to unregister IRQ");
180        }
181    }
182    ps_free(&timers->ops.malloc_ops, sizeof(timers), timers);
183}
184
185static int create_ltimer(ltimer_t *ltimer, ps_io_ops_t ops)
186{
187    assert(ltimer != NULL);
188    ltimer->get_time = get_time;
189    ltimer->get_resolution = get_resolution;
190    ltimer->set_timeout = set_timeout;
191    ltimer->reset = reset;
192    ltimer->destroy = destroy;
193
194    int error = ps_calloc(&ops.malloc_ops, 1, sizeof(hifive_timers_t), &ltimer->data);
195    if (error) {
196        return error;
197    }
198    assert(ltimer->data != NULL);
199
200    return 0;
201}
202
203static int init_ltimer(ltimer_t *ltimer)
204{
205    assert(ltimer != NULL);
206    hifive_timers_t *timers = ltimer->data;
207
208    /* setup pwm */
209    pwm_config_t config_counter = {
210        .vaddr = timers->pwm_ltimers[COUNTER_TIMER].vaddr,
211        .mode = UPCOUNTER,
212    };
213    pwm_config_t config_timeout = {
214        .vaddr = timers->pwm_ltimers[TIMEOUT_TIMER].vaddr,
215        .mode = TIMEOUT,
216    };
217
218    pwm_init(&timers->pwm_ltimers[COUNTER_TIMER].pwm, config_counter);
219    pwm_init(&timers->pwm_ltimers[TIMEOUT_TIMER].pwm, config_timeout);
220    pwm_start(&timers->pwm_ltimers[COUNTER_TIMER].pwm);
221    return 0;
222}
223
224int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t callback, void *callback_token)
225{
226
227    int error = ltimer_default_describe(ltimer, ops);
228    if (error) {
229        return error;
230    }
231
232    error = create_ltimer(ltimer, ops);
233    if (error) {
234        return error;
235    }
236
237    hifive_timers_t *timers = ltimer->data;
238    timers->ops = ops;
239    timers->user_callback = callback;
240    timers->user_callback_token = callback_token;
241    for (int i = 0; i < NUM_TIMERS; i++) {
242        timers->timer_irq_ids[i] = PS_INVALID_IRQ_ID;
243    }
244
245    for (int i = 0; i < NUM_TIMERS; i++) {
246        /* map the registers we need */
247        timers->pwm_ltimers[i].vaddr = ps_pmem_map(&ops, pmems[i], false, PS_MEM_NORMAL);
248        if (timers->pwm_ltimers[i].vaddr == NULL) {
249            destroy(ltimer->data);
250            return ENOMEM;
251        }
252
253        /* register the IRQs we need */
254        timers->callback_datas[i].ltimer = ltimer;
255        timers->callback_datas[i].irq = &irqs[i];
256        timers->callback_datas[i].irq_handler = ltimer_handle_irq;
257        timers->timer_irq_ids[i] = ps_irq_register(&ops.irq_ops, irqs[i], handle_irq_wrapper,
258                                                   &timers->callback_datas[i]);
259        if (timers->timer_irq_ids[i] < 0) {
260            destroy(ltimer->data);
261            return EIO;
262        }
263    }
264
265    error = init_ltimer(ltimer);
266    if (error) {
267        destroy(ltimer->data);
268        return error;
269    }
270
271    /* success! */
272    return 0;
273}
274
275int ltimer_default_describe(ltimer_t *ltimer, ps_io_ops_t ops)
276{
277    if (ltimer == NULL) {
278        ZF_LOGE("Timer is NULL!");
279        return EINVAL;
280    }
281
282    ltimer->get_num_irqs = get_num_irqs;
283    ltimer->get_nth_irq = get_nth_irq;
284    ltimer->get_num_pmems = get_num_pmems;
285    ltimer->get_nth_pmem = get_nth_pmem;
286    return 0;
287}
288