/*
 * 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
 */

spinlock_t rtc_lock = SPIN_LOCK_UNLOCKED;

/* 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;
}

