1/*
2 * Copyright 2017, 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/fdt.h>
20#include <platsupport/io.h>
21#include <platsupport/plat/dmt.h>
22
23#include "../../ltimer.h"
24
25/* Driver for the HiSilison hi6220 hikey Dual-timer devices.
26 *
27 * There are 9 timer devices, each implementing two downcounters for a total
28 * of 18 downcounters. These downcounters run at 19.2MHz.
29 *
30 * The 9 timer devices each have their own physical frame address, but the
31 * 2 downcounters for each device reside in the same 4K frame.
32 *
33 * We have numbered the downcounters from 0-17 as distinct logical devices.
34 */
35
36#define TCLR_ONESHOT    BIT(0)
37#define TCLR_VALUE_32   BIT(1)
38#define TCLR_INTENABLE  BIT(5)
39#define TCLR_AUTORELOAD BIT(6)
40#define TCLR_STARTTIMER BIT(7)
41#define TICKS_PER_SECOND 19200000
42#define TICKS_PER_MS    (TICKS_PER_SECOND / MS_IN_S)
43
44#define HIKEY_DUALTIMER_SECONDARY_TIMER_OFFSET (0x20)
45
46static void dmt_timer_reset(dmt_t *dmt)
47{
48    assert(dmt != NULL && dmt->dmt_map != NULL);
49    dmt_regs_t *dmt_regs = dmt->dmt_map;
50    dmt_regs->control = 0;
51
52    dmt->time_h = 0;
53}
54
55int dmt_stop(dmt_t *dmt)
56{
57    if (dmt == NULL) {
58        return EINVAL;
59    }
60    assert(dmt != NULL && dmt->dmt_map != NULL);
61    dmt_regs_t *dmt_regs = dmt->dmt_map;
62    dmt_regs->control = dmt_regs->control & ~TCLR_STARTTIMER;
63    return 0;
64}
65
66int dmt_start(dmt_t *dmt)
67{
68    if (dmt == NULL) {
69        return EINVAL;
70    }
71    assert(dmt != NULL && dmt->dmt_map != NULL);
72    dmt_regs_t *dmt_regs = dmt->dmt_map;
73    dmt_regs->control = dmt_regs->control | TCLR_STARTTIMER;
74    return 0;
75}
76
77uint64_t dmt_ticks_to_ns(uint64_t ticks)
78{
79    return ticks / TICKS_PER_MS * NS_IN_MS;
80}
81
82bool dmt_is_irq_pending(dmt_t *dmt)
83{
84    if (dmt) {
85        assert(dmt != NULL && dmt->dmt_map != NULL);
86        return !!dmt->dmt_map->ris;
87    }
88    return false;
89}
90
91int dmt_set_timeout(dmt_t *dmt, uint64_t ns, bool periodic, bool irqs)
92{
93    uint64_t ticks64 = ns * TICKS_PER_MS / NS_IN_MS;
94    if (ticks64 > UINT32_MAX) {
95        return ETIME;
96    }
97    return dmt_set_timeout_ticks(dmt, ticks64, periodic, irqs);
98}
99
100int dmt_set_timeout_ticks(dmt_t *dmt, uint32_t ticks, bool periodic, bool irqs)
101{
102    if (dmt == NULL) {
103        return EINVAL;
104    }
105    assert(dmt != NULL && dmt->dmt_map != NULL);
106
107    int flags = periodic ? TCLR_AUTORELOAD : TCLR_ONESHOT;
108    flags |= irqs ? TCLR_INTENABLE : 0;
109
110    dmt_regs_t *dmt_regs = dmt->dmt_map;
111    dmt_regs->control = 0;
112
113    /* No need to check for ticks == 0, because 0 is a valid value:
114     *
115     * Hikey Application Processor Function Description, section 2.3, "TIMERN_LOAD":
116     *   "The minimum valid value of TIMERN_LOAD is 1. If 0 is written to TIMERN_LOAD, a
117     *   timing interrupt is generated immediately."
118     *
119     * If the user supplies 0 as the argument, they'll just get an IRQ
120     * immediately.
121     */
122    if (flags & TCLR_AUTORELOAD) {
123        /* Hikey Application Processor Function Description, section 2.3, "TIMERN_BGLOAD":
124         *   "TIMERN_BGLOAD is an initial count value register in periodic mode.
125         *
126         *   In periodic mode, when the value of TIMERN_BGLOAD is updated, the
127         *   value of TIMERN_LOAD is changed to that of TIMERN_BGLOAD. However,
128         *   the timer counter does not restart counting. After the counter
129         *   decreases to 0, the value of TIMERN_LOAD (that is,
130         *   the value of TIMERN_BGLOAD) is reloaded to the counter.
131         *   dmt->regs->bgload = ticks;
132         *
133         * In other words, for periodic mode, load BGLOAD first, then write to
134         * LOAD. For oneshot mode, only write to LOAD. For good measure, write 0
135         * to BGLOAD.
136         */
137        dmt_regs->bgload = ticks;
138    } else {
139        dmt_regs->bgload = 0;
140    }
141    dmt_regs->load = ticks;
142
143    /* The TIMERN_VALUE register is read-only. */
144    dmt_regs->control = TCLR_STARTTIMER | TCLR_VALUE_32
145                        | flags;
146
147    return 0;
148}
149
150static void dmt_handle_irq(void *data, ps_irq_acknowledge_fn_t acknowledge_fn, void *ack_data)
151{
152    assert(data != NULL);
153    dmt_t *dmt = data;
154
155    /* if we are being used for timestamps */
156    if (dmt->user_cb_event == LTIMER_OVERFLOW_EVENT) {
157        dmt->time_h++;
158    }
159
160    assert(dmt->dmt_map != NULL);
161    dmt_regs_t *dmt_regs = dmt->dmt_map;
162    dmt_regs->intclr = 0x1;
163
164    ZF_LOGF_IF(acknowledge_fn(ack_data), "Failed to acknowledge the timer's interrupts");
165    if (dmt->user_cb_fn) {
166        dmt->user_cb_fn(dmt->user_cb_token, dmt->user_cb_event);
167    }
168}
169
170uint64_t dmt_get_ticks(dmt_t *dmt)
171{
172    assert(dmt != NULL && dmt->dmt_map != NULL);
173    dmt_regs_t *dmt_regs = dmt->dmt_map;
174    return dmt_regs->value;
175}
176
177uint64_t dmt_get_time(dmt_t *dmt)
178{
179    uint32_t high, low;
180
181    /* timer must be being used for timekeeping */
182    assert(dmt->user_cb_event == LTIMER_OVERFLOW_EVENT);
183
184    /* dmt is a down counter, invert the result */
185    high = dmt->time_h;
186    low = UINT32_MAX - dmt_get_ticks(dmt);
187
188    /* check after fetching low to see if we've missed a high bit */
189    if (dmt_is_irq_pending(dmt)) {
190        high += 1;
191        assert(high != 0);
192    }
193
194    uint64_t ticks = (((uint64_t) high << 32llu) | low);
195    return dmt_ticks_to_ns(ticks);
196}
197
198void dmt_destroy(dmt_t *dmt)
199{
200    int error;
201    if (dmt->irq_id != PS_INVALID_IRQ_ID) {
202        error = ps_irq_unregister(&dmt->ops.irq_ops, dmt->irq_id);
203        ZF_LOGF_IF(error, "Failed to unregister IRQ");
204    }
205    if (dmt->dmt_map != NULL) {
206        dmt_stop(dmt);
207    }
208    if (dmt->dmt_map_base != NULL) {
209        /* use base because dmt_map is adjusted based on whether secondary */
210        ps_pmem_unmap(&dmt->ops, dmt->pmem, (void *) dmt->dmt_map_base);
211    }
212}
213
214int dmt_init(dmt_t *dmt, ps_io_ops_t ops, dmt_config_t config)
215{
216    int error;
217
218    if (dmt == NULL) {
219        ZF_LOGE("dmt cannot be null");
220        return EINVAL;
221    }
222
223    dmt->ops = ops;
224    dmt->user_cb_fn = config.user_cb_fn;
225    dmt->user_cb_token = config.user_cb_token;
226    dmt->user_cb_event = config.user_cb_event;
227
228    error = helper_fdt_alloc_simple(
229                &ops, config.fdt_path,
230                DMT_REG_CHOICE, DMT_IRQ_CHOICE,
231                &dmt->dmt_map_base, &dmt->pmem, &dmt->irq_id,
232                dmt_handle_irq, dmt
233            );
234    if (error) {
235        ZF_LOGE("Simple fdt alloc helper failed");
236        return error;
237    }
238
239    dmt->dmt_map = dmt->dmt_map_base;
240
241    dmt_timer_reset(dmt);
242    return 0;
243}
244
245/* initialise dmt using the base address of dmtp, so that we do not attempt to map
246 * that base address again but instead re-use it. */
247int dmt_init_secondary(dmt_t *dmt, dmt_t *dmtp, ps_io_ops_t ops, dmt_config_t config)
248{
249    int error;
250
251    if (dmt == NULL || dmtp == NULL) {
252        ZF_LOGE("dmt or dmtp cannot be null");
253        return EINVAL;
254    }
255
256    dmt->ops = ops;
257    dmt->user_cb_fn = config.user_cb_fn;
258    dmt->user_cb_token = config.user_cb_token;
259    dmt->user_cb_event = config.user_cb_event;
260
261    /* so that destroy does not try to unmap twice */
262    dmt->dmt_map_base = NULL;
263    /* First sub-device is at offset 0, second sub-device is
264     * at offset 0x20 within the same page. */
265    dmt->dmt_map = (void *)((uintptr_t) dmtp->dmt_map_base) + HIKEY_DUALTIMER_SECONDARY_TIMER_OFFSET;
266    dmt->irq_id = PS_INVALID_IRQ_ID;
267
268    /* Gather FDT info */
269    ps_fdt_cookie_t *cookie = NULL;
270    error = ps_fdt_read_path(&ops.io_fdt, &ops.malloc_ops, config.fdt_path, &cookie);
271    if (error) {
272        ZF_LOGE("Failed to read path (%d, %s)", error, config.fdt_path);
273        return error;
274    }
275
276    /* choose irq 1 because secondary */
277    irq_id_t irq_id = ps_fdt_index_register_irq(&ops, cookie, 1, dmt_handle_irq, dmt);
278    if (irq_id <= PS_INVALID_IRQ_ID) {
279        ZF_LOGE("Failed to register irqs (%d)", irq_id);
280        return irq_id;
281    }
282
283    error = ps_fdt_cleanup_cookie(&ops.malloc_ops, cookie);
284    if (error) {
285        ZF_LOGE("Failed to clean up cookie (%d)", error);
286        return error;
287    }
288
289    dmt->irq_id = irq_id;
290
291    dmt_timer_reset(dmt);
292    return 0;
293}
294