1/**
2 * \file
3 * \brief x86 legacy timer driver.
4 */
5
6/*
7 * Copyright (c) 2007, 2008, 2009, ETH Zurich.
8 * All rights reserved.
9 *
10 * This file is distributed under the terms in the attached LICENSE file.
11 * If you do not find this file, copies can be found by writing to:
12 * ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group.
13 */
14
15#include <stdio.h>
16#include <barrelfish/barrelfish.h>
17#include <pci/pci.h>
18#include <dev/lpc_timer_dev.h>
19#include "timer.h"
20
21#include "lpc_timer_debug.h"
22
23#define TIMER_IOBASE    0x40
24
25/// Period of LPC timer 0 counter, in nanoseconds
26#define TIMER0_PERIOD_NS    838
27
28/// Maximum value of (16-bit) count
29#define TIMER_MAX_COUNT     ((1 << 16) - 1)
30
31
32/*************************************************************//**
33 * \defGroup LocalStates Local states
34 *
35 * @{
36 *
37 ****************************************************************/
38
39static struct lpc_timer_t timer;        ///< Mackerel state for timer registers
40static timer_handler_fn timer_handler;  ///< Expiry handler
41
42/// Remaining value of current timeout, in timer ticks
43static uint64_t timer_remainder;
44
45/*
46 * @}
47 */
48
49
50/*************************************************************//**
51 * \defGroup Main Main
52 *
53 * @{
54 *
55 ****************************************************************/
56
57
58/**
59 * \brief Set hardware timer mode and count value
60 *
61 * Sets timer 0 as either a rate generator (mode 2) or one-shot timer (mode 0)
62 * and sets its value. This function currently does not deal with any other
63 * timers.
64 *
65 * \param count  Count for oneshot timer, rate for ticker
66 * \param periodic True for a periodic timer, false for oneshot
67 */
68static void timer0_set(uint16_t count, bool periodic)
69{
70	/*
71    LPC_DEBUG("timer0_set: programming %s timer for %u ticks\n",
72              periodic ? "periodic" : "one-shot", count);
73*/
74    struct lpc_timer_tcw_t tcw = {
75        .bcd = 0,                       // Binary mode (no BCD)
76        .mode = periodic ? lpc_timer_rtgen : lpc_timer_oseoc, // Operating mode
77        .rwsel = lpc_timer_lmsb,        // First MSB, then LSB
78        .select = lpc_timer_c0          // Select counter 0
79    };
80
81    // Prepare timer 0 to set its count
82    lpc_timer_tcw_wr(&timer, tcw);
83
84    if (count > 0) {
85        // Set the count/rate (LSB, then MSB)
86        lpc_timer_cntacc0_wr(&timer, count & 0xff);
87        lpc_timer_cntacc0_wr(&timer, count >> 8);
88    }
89}
90
91
92/**
93 * \brief Read current value of timer
94 *
95 * \returns the current value of timer 0
96 */
97static uint16_t timer0_read(void)
98{
99    uint16_t val;
100    lpc_timer_sbyte_fmt_t status;
101
102    do {
103        // 1. Issue read back command to read the status and count of the counter
104        struct lpc_timer_rdbk_cmd_t cmd = {
105            .c0 = 1, .c1 = 0, .c2 = 0,  // select counter 0 only
106            .stat = 0, .count = 0       // latch both status and count
107        };
108        lpc_timer_rdbk_cmd_wr(&timer, cmd);
109
110        // 2. Read status
111        status = lpc_timer_sbyte_fmt0_rd(&timer);
112
113        // 3. Read value latched value (LSB, then MSB)
114        // (we must do this even if the status shows an invalid count)
115        val = lpc_timer_cntacc0_rd(&timer) << 8;
116        val |= lpc_timer_cntacc0_rd(&timer);
117
118        LPC_DEBUG("timer0_read:%s %u ticks remaining\n",
119                  status.cnt_stat ? " null count read," : "", val);
120
121    // if we got unlucky, and read the counter before it had finished loading,
122    // the count may be invalid ("null count"), so we repeat the whole rigmarole
123    } while (status.cnt_stat);
124
125    return val;
126}
127
128
129/**
130 * \brief message handler for timer interrupt
131 *
132 *
133 *
134 */
135static void lpc_timer_interrupt(void *arg)
136{
137//    LPC_DEBUG("interrupt\n");
138
139    // reprogram timer if remainder is set
140    if (timer_remainder != 0) {
141        if (timer_remainder > TIMER_MAX_COUNT) {
142            timer0_set(TIMER_MAX_COUNT, false);
143            timer_remainder -= TIMER_MAX_COUNT;
144        } else {
145            timer0_set(timer_remainder, false);
146            timer_remainder = 0;
147        }
148    // otherwise we have a timeout: run the handler
149    } else if (timer_handler != NULL) {
150        timer_handler();
151    } else {
152        LPC_DEBUG("timer_interrupt: no handler\n");
153    }
154}
155
156
157/**
158 * \brief
159 *
160 *
161 *
162 */
163void lpc_timer_register_handler(timer_handler_fn handler)
164{
165    LPC_DEBUG("timer_register_handler: called\n");
166
167    timer_handler = handler;
168
169    LPC_DEBUG("timer_register_handler: terminated\n");
170}
171
172static void timer_init(void)
173{
174    LPC_DEBUG("timer_init: called\n");
175
176    lpc_timer_initialize(&timer, TIMER_IOBASE);
177    timer0_set(0, false);
178
179    timer_init_complete();
180
181    LPC_DEBUG("timer_init: terminated\n");
182}
183
184/**
185 * \brief Initialize timer driver.
186 *
187 * Initializes the timer (and driver) to a known state.
188 */
189errval_t lpc_timer_init(void)
190{
191    int r = pci_client_connect();
192    assert(r == 0); // XXX
193
194    return pci_register_legacy_driver_irq_cap(timer_init, TIMER_IOBASE,
195                                          TIMER_IOBASE + 4, 0,
196                                          lpc_timer_interrupt, NULL);
197}
198
199
200/**
201 * \brief Set the timeout.
202 *
203 * \param us Timeout in microseconds
204 */
205void lpc_timer_set(uint64_t us)
206{
207    // the requested time must not exceed the maximum possible interval
208    assert (us < UINT64_MAX / (1000 / TIMER0_PERIOD_NS));
209
210    // convert to timer count value
211    uint64_t count = us * 1000 / TIMER0_PERIOD_NS;
212
213    // program hardware timer for max(TIMER_MAX_COUNT, count)
214    if (count > TIMER_MAX_COUNT) {
215        timer_remainder = count - TIMER_MAX_COUNT;
216        count = TIMER_MAX_COUNT;
217    } else {
218        timer_remainder = 0;
219    }
220
221    timer0_set(count, false);
222
223    LPC_DEBUG("lpc_timer_set: %lu us -> %lu + %lu ticks\n",
224              us, count, timer_remainder);
225}
226
227
228/**
229 * \brief Read the current value of the timer
230 *
231 * \returns Remaining time of pending timer, in microseconds
232 */
233uint64_t lpc_timer_read(void)
234{
235    uint16_t val = timer0_read();
236    uint64_t us = (val + timer_remainder) * TIMER0_PERIOD_NS / 1000;
237
238    LPC_DEBUG("lpc_timer_read: %u ticks + %lu remaining = %lu us\n",
239              val, timer_remainder, us);
240
241    return us;
242}
243
244/*
245 * @}
246 */
247