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