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#include <stdio.h>
13#include <assert.h>
14#include <errno.h>
15
16#include <utils/util.h>
17#include <utils/time.h>
18
19#include <platsupport/plat/sp804.h>
20
21#include "../../ltimer.h"
22
23/* This file is mostly the same as the dmt.c file for the hikey.
24 * Consider to merge the two files as a single driver file for
25 * SP804.
26 */
27
28#define TCLR_ONESHOT    BIT(0)
29#define TCLR_VALUE_32   BIT(1)
30#define TCLR_INTENABLE  BIT(5)
31#define TCLR_AUTORELOAD BIT(6)
32#define TCLR_STARTTIMER BIT(7)
33/* It looks like the FVP does not emulate time accruately. Thus, pick
34 *  a small Hz that triggers interrupts in a reasonable time */
35#define TICKS_PER_SECOND 35000
36#define TICKS_PER_MS    (TICKS_PER_SECOND / MS_IN_S)
37
38static void sp804_timer_reset(sp804_t *sp804)
39{
40    assert(sp804 != NULL && sp804->sp804_map != NULL);
41    sp804_regs_t *sp804_regs = sp804->sp804_map;
42    sp804_regs->control = 0;
43
44    sp804->time_h = 0;
45}
46
47int sp804_stop(sp804_t *sp804)
48{
49    if (sp804 == NULL) {
50        return EINVAL;
51    }
52    assert(sp804->sp804_map != NULL);
53    sp804_regs_t *sp804_regs = sp804->sp804_map;
54    sp804_regs->control = sp804_regs->control & ~TCLR_STARTTIMER;
55    return 0;
56}
57
58int sp804_start(sp804_t *sp804)
59{
60    if (sp804 == NULL) {
61        return EINVAL;
62    }
63    assert(sp804->sp804_map != NULL);
64    sp804_regs_t *sp804_regs = sp804->sp804_map;
65    sp804_regs->control = sp804_regs->control | TCLR_STARTTIMER;
66    return 0;
67}
68
69uint64_t sp804_ticks_to_ns(uint64_t ticks)
70{
71    return ticks / TICKS_PER_MS * NS_IN_MS;
72}
73
74bool sp804_is_irq_pending(sp804_t *sp804)
75{
76    if (sp804) {
77        assert(sp804->sp804_map != NULL);
78        return !!sp804->sp804_map->ris;
79    }
80    return false;
81}
82
83int sp804_set_timeout(sp804_t *sp804, uint64_t ns, bool periodic, bool irqs)
84{
85    uint64_t ticks64 = ns * TICKS_PER_MS / NS_IN_MS;
86    if (ticks64 > UINT32_MAX) {
87        return ETIME;
88    }
89    return sp804_set_timeout_ticks(sp804, ticks64, periodic, irqs);
90}
91
92int sp804_set_timeout_ticks(sp804_t *sp804, uint32_t ticks, bool periodic, bool irqs)
93{
94    if (sp804 == NULL) {
95        return EINVAL;
96    }
97    int flags = periodic ? TCLR_AUTORELOAD : TCLR_ONESHOT;
98    flags |= irqs ? TCLR_INTENABLE : 0;
99
100    assert(sp804->sp804_map != NULL);
101    sp804_regs_t *sp804_regs = sp804->sp804_map;
102    sp804_regs->control = 0;
103
104    if (flags & TCLR_AUTORELOAD) {
105        sp804_regs->bgload = ticks;
106    } else {
107        sp804_regs->bgload = 0;
108    }
109    sp804_regs->load = ticks;
110
111    /* The TIMERN_VALUE register is read-only. */
112    sp804_regs->control = TCLR_STARTTIMER | TCLR_VALUE_32
113                          | flags;
114
115    return 0;
116}
117
118static void sp804_handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data)
119{
120    assert(data != NULL);
121    sp804_t *sp804 = data;
122
123    if (sp804->user_cb_event == LTIMER_OVERFLOW_EVENT) {
124        sp804->time_h++;
125    }
126
127    sp804_regs_t *sp804_regs = sp804->sp804_map;
128    sp804_regs->intclr = 0x1;
129
130    ZF_LOGF_IF(acknowledge_fn(ack_data), "Failed to acknowledge the timer's interrupts");
131    if (sp804->user_cb_fn) {
132        sp804->user_cb_fn(sp804->user_cb_token, sp804->user_cb_event);
133    }
134}
135
136uint64_t sp804_get_ticks(sp804_t *sp804)
137{
138    assert(sp804 != NULL && sp804->sp804_map != NULL);
139    sp804_regs_t *sp804_regs = sp804->sp804_map;
140    return sp804_regs->value;
141}
142
143uint64_t sp804_get_time(sp804_t *sp804)
144{
145    uint32_t high, low;
146
147    /* timer must be being used for timekeeping */
148    assert(sp804->user_cb_event == LTIMER_OVERFLOW_EVENT);
149
150    /* sp804 is a down counter, invert the result */
151    high = sp804->time_h;
152    low = UINT32_MAX - sp804_get_ticks(sp804);
153
154    /* check after fetching low to see if we've missed a high bit */
155    if (sp804_is_irq_pending(sp804)) {
156        high += 1;
157        assert(high != 0);
158    }
159
160    uint64_t ticks = (((uint64_t) high << 32llu) | low);
161    return sp804_ticks_to_ns(ticks);
162}
163
164void sp804_destroy(sp804_t *sp804)
165{
166    int error;
167    if (sp804->irq_id != PS_INVALID_IRQ_ID) {
168        error = ps_irq_unregister(&sp804->ops.irq_ops, sp804->irq_id);
169        ZF_LOGF_IF(error, "Failed to unregister IRQ");
170    }
171    if (sp804->sp804_map != NULL) {
172        sp804_stop(sp804);
173        ps_pmem_unmap(&sp804->ops, sp804->pmem, (void *) sp804->sp804_map);
174    }
175}
176
177int sp804_init(sp804_t *sp804, ps_io_ops_t ops, sp804_config_t config)
178{
179    int error;
180
181    if (sp804 == NULL) {
182        ZF_LOGE("sp804 cannot be null");
183        return EINVAL;
184    }
185
186    sp804->ops = ops;
187    sp804->user_cb_fn = config.user_cb_fn;
188    sp804->user_cb_token = config.user_cb_token;
189    sp804->user_cb_event = config.user_cb_event;
190
191    error = helper_fdt_alloc_simple(
192                &ops, config.fdt_path,
193                SP804_REG_CHOICE, SP804_IRQ_CHOICE,
194                (void *) &sp804->sp804_map, &sp804->pmem, &sp804->irq_id,
195                sp804_handle_irq, sp804
196            );
197    if (error) {
198        ZF_LOGE("Simple fdt alloc helper failed");
199        return error;
200    }
201
202    sp804_timer_reset(sp804);
203    return 0;
204}
205