summaryrefslogtreecommitdiffstats
path: root/arch/mips/philips/nino/time.c
blob: b3bc87e7ea15c8807226c71ed00edaff1c7a5bf6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/*
 *  linux/arch/mips/philips/nino/time.c
 *
 *  Copyright (C) 1999 Harald Koerfgen (Harald.Koerfgen@home.ivm.de)
 *  Copyright (C) 2000 Pavel Machek (pavel@suse.cz)
 *  Copyright (C) 2001 Steven Hill (sjhill@realitydiluted.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 *  Time handling details for PR31700.
 */
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/param.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/timex.h>
#include <linux/delay.h>
#include <asm/philips/pr31700.h>

extern volatile unsigned long wall_jiffies;
extern rwlock_t xtime_lock;

static struct timeval xbase;

#define USECS_PER_JIFFY (1000000/HZ)

/*
 * Poll the Interrupt Status Registers
 */
#undef POLL_STATUS

static unsigned long do_gettimeoffset(void)
{
    /*
     * This is a kludge
     */
    return 0;
}

static
void inline readRTC(unsigned long *high, unsigned long *low)
{
	/* read twice, and keep reading till we find two
	 * the same pairs. This is needed in case the RTC
	 * was updating its registers and we read a old
	 * High but a new Low. */
	do {
		*high = RTChigh & RTC_HIGHMASK;
		*low = RTClow;
	} while (*high != (RTChigh & RTC_HIGHMASK) || RTClow!=*low);
}

/*
 * This version of gettimeofday has near millisecond resolution.
 */
void do_gettimeofday(struct timeval *tv)
{
    unsigned long flags;
    unsigned long high, low;

    read_lock_irqsave(&xtime_lock, flags);
    // 40 bit RTC, driven by 32khz source:
    // +-----------+-----------------------------------------+
    // | HHHH.HHHH | LLLL.LLLL.LLLL.LLLL.LMMM.MMMM.MMMM.MMMM |
    // +-----------+-----------------------------------------+
    readRTC(&high,&low);
    tv->tv_sec  = (high << 17) | (low >> 15);
    tv->tv_usec = (low % 32768) * 1953 / 64;
    tv->tv_sec += xbase.tv_sec;
    tv->tv_usec += xbase.tv_usec;

    tv->tv_usec += do_gettimeoffset();

    /*
     * xtime is atomically updated in timer_bh. lost_ticks is
     * nonzero if the timer bottom half hasnt executed yet.
     */
    if (jiffies - wall_jiffies)
	tv->tv_usec += USECS_PER_JIFFY;

    read_unlock_irqrestore(&xtime_lock, flags);

    if (tv->tv_usec >= 1000000) {
	tv->tv_usec -= 1000000;
	tv->tv_sec++;
    }
}

void do_settimeofday(struct timeval *tv)
{
    write_lock_irq(&xtime_lock);
    /* This is revolting. We need to set the xtime.tv_usec
     * correctly. However, the value in this location is
     * is value at the last tick.
     * Discover what correction gettimeofday
     * would have done, and then undo it!
     */
    tv->tv_usec -= do_gettimeoffset();

    if (tv->tv_usec < 0) {
	tv->tv_usec += 1000000;
	tv->tv_sec--;
    }

    /* reset RTC to 0 (real time is xbase + RTC) */
    xbase = *tv;
    RTCtimerControl |=  TIM_RTCCLEAR;
    RTCtimerControl &= ~TIM_RTCCLEAR;
    RTCalarmHigh = RTCalarmLow = ~0UL;

    xtime = *tv;
    time_state = TIME_BAD;
    time_maxerror = MAXPHASE;
    time_esterror = MAXPHASE;
    write_unlock_irq(&xtime_lock);
}

static int set_rtc_mmss(unsigned long nowtime)
{
    int retval = 0;

    return retval;
}

/* last time the cmos clock got updated */
static long last_rtc_update = 0;

/*
 * timer_interrupt() needs to keep up the real-time clock,
 * as well as call the "do_timer()" routine every clocktick
 */

int do_write = 1;

static void
timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
#ifdef POLL_STATUS
    static unsigned long old_IntStatus1 = 0;
    static unsigned long old_IntStatus3 = 0;
    static unsigned long old_IntStatus4 = 0;
    static unsigned long old_IntStatus5 = 0;
    static int counter = 0;
    int i;

    new_spircv = SPIData & 0xff;
    if ((old_spircv != new_spircv) && (new_spircv != 0xff)) {
	    printk( "SPIData changed: %x\n", new_spircv );
    }
    old_spircv = new_spircv;
    if (do_write)
	    SPIData = 0;
#endif

    if (!user_mode(regs)) {
	if (prof_buffer && current->pid) {
	    extern int _stext;
	    unsigned long pc = regs->cp0_epc;

	    pc -= (unsigned long) &_stext;
	    pc >>= prof_shift;
	    /*
	     * Dont ignore out-of-bounds pc values silently,
	     * put them into the last histogram slot, so if
	     * present, they will show up as a sharp peak.
	     */
	    if (pc > prof_len - 1)
		pc = prof_len - 1;
		atomic_inc((atomic_t *) & prof_buffer[pc]);
	    }
    }

    /*
     * aaaand... action!
     */
    do_timer(regs);

    /*
     * If we have an externally syncronized Linux clock, then update
     * CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be
     * called as close as possible to 500 ms before the new second starts.
     */
    if (time_state != TIME_BAD && xtime.tv_sec > last_rtc_update + 660 &&
	xtime.tv_usec > 500000 - (tick >> 1) &&
	xtime.tv_usec < 500000 + (tick >> 1))
    {
	if (set_rtc_mmss(xtime.tv_sec) == 0)
	    last_rtc_update = xtime.tv_sec;
	else
	    last_rtc_update = xtime.tv_sec - 600;  /* do it again in 60 s */
    }
}

static struct irqaction irq0 = {timer_interrupt, SA_INTERRUPT, 0,
 			 "timer", NULL, NULL};

void (*board_time_init) (struct irqaction * irq);

int __init time_init(void)
{
    struct timeval starttime;

    starttime.tv_sec = mktime(2000, 1, 1, 0, 0, 0);
    starttime.tv_usec = 0;
    do_settimeofday(&starttime);

    board_time_init(&irq0);

    return 0;
}