1/*
2 * Copyright 2001 MontaVista Software Inc.
3 * Author: jsun@mvista.com or jsun@junsun.net
4 *
5 * rtc and time ops for vr4181.	 Part of code is drived from
6 * linux-vr, originally written	 by Bradley D. LaRonde & Michael Klar.
7 *
8 * This program is free software; you can redistribute	it and/or modify it
9 * under  the terms of	the GNU General	 Public License as published by the
10 * Free Software Foundation;  either version 2 of the  License, or (at your
11 * option) any later version.
12 *
13 */
14
15#include <linux/kernel.h>
16#include <linux/spinlock.h>
17#include <linux/param.h>			/* for HZ */
18#include <linux/time.h>
19#include <linux/interrupt.h>
20
21#include <asm/system.h>
22#include <asm/time.h>
23
24#include <asm/vr4181/vr4181.h>
25
26#define COUNTS_PER_JIFFY ((32768 + HZ/2) / HZ)
27
28/*
29 * RTC ops
30 */
31
32extern spinlock_t rtc_lock;
33
34/* per VR41xx docs, bad data can be read if between 2 counts */
35static inline unsigned short
36read_time_reg(volatile unsigned short *reg)
37{
38	unsigned short value;
39	do {
40		value = *reg;
41		barrier();
42	} while (value != *reg);
43	return value;
44}
45
46static unsigned long
47vr4181_rtc_get_time(void)
48{
49	unsigned short regh, regm, regl;
50
51	// why this crazy order, you ask?  to guarantee that neither m
52	// nor l wrap before all 3 read
53	do {
54		regm = read_time_reg(VR4181_ETIMEMREG);
55		barrier();
56		regh = read_time_reg(VR4181_ETIMEHREG);
57		barrier();
58		regl = read_time_reg(VR4181_ETIMELREG);
59	} while (regm != read_time_reg(VR4181_ETIMEMREG));
60	return ((regh << 17) | (regm << 1) | (regl >> 15));
61}
62
63static int
64vr4181_rtc_set_time(unsigned long timeval)
65{
66	unsigned short intreg;
67	unsigned long flags;
68
69	spin_lock_irqsave(&rtc_lock, flags);
70	intreg = *VR4181_RTCINTREG & 0x05;
71	barrier();
72	*VR4181_ETIMELREG = timeval << 15;
73	*VR4181_ETIMEMREG = timeval >> 1;
74	*VR4181_ETIMEHREG = timeval >> 17;
75	barrier();
76	// assume that any ints that just triggered are invalid, since the
77	// time value is written non-atomically in 3 separate regs
78	*VR4181_RTCINTREG = 0x05 ^ intreg;
79	spin_unlock_irqrestore(&rtc_lock, flags);
80
81	return 0;
82}
83
84
85/*
86 * timer interrupt routine (wrapper)
87 *
88 * we need our own interrupt routine because we need to clear
89 * RTC1 interrupt.
90 */
91static void
92vr4181_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
93{
94	/* Clear the interrupt. */
95	*VR4181_RTCINTREG = 0x2;
96
97	/* call the generic one */
98	timer_interrupt(irq, dev_id, regs);
99}
100
101
102/*
103 * vr4181_time_init:
104 *
105 * We pick the following choices:
106 *   . we use elapsed timer as the RTC.	 We set some reasonable init data since
107 *     it does not persist across reset
108 *   . we use RTC1 as the system timer interrupt source.
109 *   . we use CPU counter for fast_gettimeoffset and we calivrate the cpu
110 *     frequency.  In other words, we use calibrate_div64_gettimeoffset().
111 *   . we use our own timer interrupt routine which clears the interrupt
112 *     and then calls the generic high-level timer interrupt routine.
113 *
114 */
115
116extern int setup_irq(unsigned int irq, struct irqaction *irqaction);
117
118static void
119vr4181_timer_setup(struct irqaction *irq)
120{
121	/* over-write the handler to be our own one */
122	irq->handler = vr4181_timer_interrupt;
123
124	/* sets up the frequency */
125	*VR4181_RTCL1LREG = COUNTS_PER_JIFFY;
126	*VR4181_RTCL1HREG = 0;
127
128	/* and ack any pending ints */
129	*VR4181_RTCINTREG = 0x2;
130
131	/* setup irqaction */
132	setup_irq(VR4181_IRQ_INT1, irq);
133
134}
135
136void
137vr4181_init_time(void)
138{
139	/* setup hookup functions */
140	rtc_get_time = vr4181_rtc_get_time;
141	rtc_set_time = vr4181_rtc_set_time;
142
143	board_timer_setup = vr4181_timer_setup;
144}
145
146