summaryrefslogtreecommitdiff
path: root/arch/mips/vr4181/common/time.c
blob: 17814076b6f407b932f1813beeb8f3d86d99919f (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
/*
 * Copyright 2001 MontaVista Software Inc.
 * Author: jsun@mvista.com or jsun@junsun.net
 *
 * rtc and time ops for vr4181.	 Part of code is drived from
 * linux-vr, originally written	 by Bradley D. LaRonde & Michael Klar.
 *
 * This program is free software; you can redistribute	it and/or modify it
 * under  the terms of	the GNU General	 Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 *
 */

#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/param.h>			/* for HZ */
#include <linux/time.h>
#include <linux/interrupt.h>

#include <asm/system.h>
#include <asm/time.h>

#include <asm/vr4181/vr4181.h>

#define COUNTS_PER_JIFFY ((32768 + HZ/2) / HZ)

/*
 * RTC ops
 */

DEFINE_SPINLOCK(rtc_lock);

/* per VR41xx docs, bad data can be read if between 2 counts */
static inline unsigned short
read_time_reg(volatile unsigned short *reg)
{
	unsigned short value;
	do {
		value = *reg;
		barrier();
	} while (value != *reg);
	return value;
}

static unsigned long
vr4181_rtc_get_time(void)
{
	unsigned short regh, regm, regl;

	// why this crazy order, you ask?  to guarantee that neither m
	// nor l wrap before all 3 read
	do {
		regm = read_time_reg(VR4181_ETIMEMREG);
		barrier();
		regh = read_time_reg(VR4181_ETIMEHREG);
		barrier();
		regl = read_time_reg(VR4181_ETIMELREG);
	} while (regm != read_time_reg(VR4181_ETIMEMREG));
	return ((regh << 17) | (regm << 1) | (regl >> 15));
}

static int
vr4181_rtc_set_time(unsigned long timeval)
{
	unsigned short intreg;
	unsigned long flags;

	spin_lock_irqsave(&rtc_lock, flags);
	intreg = *VR4181_RTCINTREG & 0x05;
	barrier();
	*VR4181_ETIMELREG = timeval << 15;
	*VR4181_ETIMEMREG = timeval >> 1;
	*VR4181_ETIMEHREG = timeval >> 17;
	barrier();
	// assume that any ints that just triggered are invalid, since the
	// time value is written non-atomically in 3 separate regs
	*VR4181_RTCINTREG = 0x05 ^ intreg;
	spin_unlock_irqrestore(&rtc_lock, flags);

	return 0;
}


/*
 * timer interrupt routine (wrapper)
 *
 * we need our own interrupt routine because we need to clear
 * RTC1 interrupt.
 */
static void
vr4181_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	/* Clear the interrupt. */
	*VR4181_RTCINTREG = 0x2;

	/* call the generic one */
	timer_interrupt(irq, dev_id, regs);
}


/*
 * vr4181_time_init:
 *
 * We pick the following choices:
 *   . we use elapsed timer as the RTC.	 We set some reasonable init data since
 *     it does not persist across reset
 *   . we use RTC1 as the system timer interrupt source.
 *   . we use CPU counter for fast_gettimeoffset and we calivrate the cpu
 *     frequency.  In other words, we use calibrate_div64_gettimeoffset().
 *   . we use our own timer interrupt routine which clears the interrupt
 *     and then calls the generic high-level timer interrupt routine.
 *
 */

extern int setup_irq(unsigned int irq, struct irqaction *irqaction);

static void
vr4181_timer_setup(struct irqaction *irq)
{
	/* over-write the handler to be our own one */
	irq->handler = vr4181_timer_interrupt;

	/* sets up the frequency */
	*VR4181_RTCL1LREG = COUNTS_PER_JIFFY;
	*VR4181_RTCL1HREG = 0;

	/* and ack any pending ints */
	*VR4181_RTCINTREG = 0x2;

	/* setup irqaction */
	setup_irq(VR4181_IRQ_INT1, irq);

}

void
vr4181_init_time(void)
{
	/* setup hookup functions */
	rtc_get_time = vr4181_rtc_get_time;
	rtc_set_time = vr4181_rtc_set_time;

	board_timer_setup = vr4181_timer_setup;
}