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
13#include <errno.h>
14#include <platsupport/io.h>
15#include <platsupport/plat/pit.h>
16
17#include <inttypes.h>
18#include <stdbool.h>
19#include <stdio.h>
20#include <stdlib.h>
21
22#include <utils/util.h>
23
24#define PIT_IOPORT_CHANNEL(x) (0x40 + x) /* valid channels are 0, 1, 2. we'll be using 0 exclusively, though */
25#define PIT_IOPORT_COMMAND  0x43
26#define PIT_IOPORT_PITCR    PIT_IOPORT_COMMAND
27
28/* PIT command register macros */
29#define PITCR_SET_CHANNEL(x, channel)   (((channel) << 6) | x)
30#define PITCR_SET_OP_MODE(x, mode)      (((mode) << 1)    | x)
31#define PITCR_SET_ACCESS_MODE(x, mode)  (((mode) << 4)    | x)
32
33#define PITCR_LATCH_CHANNEL(channel)    PITCR_SET_CHANNEL(0, channel)
34
35#define PITCR_ACCESS_LOW   0x1
36#define PITCR_ACCESS_HIGH  0x2
37#define PITCR_ACCESS_LOHI  0x3
38
39#define PITCR_MODE_ONESHOT   0x0
40#define PITCR_MODE_PERIODIC  0x2
41#define PITCR_MODE_SQUARE    0x3
42#define PITCR_MODE_SWSTROBE  0x4
43
44/* helper functions */
45static inline int
46set_pit_mode(ps_io_port_ops_t *ops, uint8_t channel, uint8_t mode)
47{
48    return ps_io_port_out(ops, PIT_IOPORT_PITCR, 1,
49                          PITCR_SET_CHANNEL(PITCR_SET_OP_MODE(PITCR_SET_ACCESS_MODE(0, PITCR_ACCESS_LOHI), mode), channel));
50}
51
52static inline int
53configure_pit(pit_t *pit, uint8_t mode, uint64_t ns)
54{
55
56    int error;
57
58    if (ns > PIT_MAX_NS || ns < PIT_MIN_NS) {
59        ZF_LOGV("ns invalid for programming PIT %"PRIu64" <= %"PRIu64" <= %"PRIu64"\n",
60                (uint64_t)PIT_MIN_NS, ns, (uint64_t)PIT_MAX_NS);
61        return EINVAL;
62    }
63
64    uint64_t ticks = PIT_NS_TO_TICKS(ns);
65
66    /* configure correct mode */
67    error = set_pit_mode(&pit->ops, 0, mode);
68    if (error) {
69        ZF_LOGE("ps_io_port_out failed on channel %u\n", PIT_IOPORT_CHANNEL(0));
70        return EIO;
71    }
72
73    /* program timeout */
74    error = ps_io_port_out(&pit->ops, PIT_IOPORT_CHANNEL(0), 1, (uint8_t) (ticks & 0xFF));
75    if (error) {
76        ZF_LOGE("ps_io_port_out failed on channel %u\n", PIT_IOPORT_CHANNEL(0));
77        return EIO;
78    }
79
80    error = ps_io_port_out(&pit->ops, PIT_IOPORT_CHANNEL(0), 1, (uint8_t) (ticks >> 8) & 0xFF);
81    if (error) {
82        ZF_LOGE("ps_io_port_out failed on channel %u\n", PIT_IOPORT_CHANNEL(0));
83        return EIO;
84    }
85
86    return 0;
87}
88
89/* interface functions */
90
91int pit_cancel_timeout(pit_t *pit)
92{
93    /* There's no way to disable the PIT, so we set it up in mode 0 and don't
94     * start it
95     */
96    return set_pit_mode(&pit->ops, 0, PITCR_MODE_ONESHOT);
97}
98
99uint64_t pit_get_time(pit_t *pit)
100{
101    int error = ps_io_port_out(&pit->ops, PIT_IOPORT_CHANNEL(3), 1, 0);
102    if (error) {
103        return 0;
104    }
105
106    uint32_t low, high;
107
108    /* Read the low 8 bits of the current timer value. */
109    error = ps_io_port_in(&pit->ops, PIT_IOPORT_CHANNEL(0), 1, &low);
110    if (error) {
111        return 0;
112    }
113
114    /* Read the high 8 bits of the current timer value. */
115    error = ps_io_port_in(&pit->ops, PIT_IOPORT_CHANNEL(0), 1, &high);
116    if (error) {
117        return 0;
118    }
119
120    /* Assemble the high and low 8 bits using (high << 8) + low, and then convert to nanoseconds. */
121    return ((high << 8) + low) * NS_IN_S / TICKS_PER_SECOND;
122}
123
124int pit_set_timeout(pit_t *pit, uint64_t ns, bool periodic)
125{
126    uint32_t mode = periodic ? PITCR_MODE_PERIODIC : PITCR_MODE_ONESHOT;
127    return configure_pit(pit, mode, ns);
128}
129
130/* initialisation function */
131int pit_init(pit_t *pit, ps_io_port_ops_t io_port_ops)
132{
133    if (pit == NULL) {
134        return EINVAL;
135    }
136
137    /* check that the io port ops are at least implemented as these are absolutely required */
138    if (!io_port_ops.io_port_in_fn || !io_port_ops.io_port_out_fn) {
139        return EINVAL;
140    }
141
142    pit->ops = io_port_ops;
143    return 0;
144}
145