/*
 * Carsten Langgaard, carstenl@mips.com
 * Copyright (C) 1999,2000 MIPS Technologies, Inc.  All rights reserved.
 *
 *  This program is free software; you can distribute it and/or modify it
 *  under the terms of the GNU General Public License (Version 2) as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope it will be useful, but WITHOUT
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
 *
 * Setting up the clock on the MIPS boards.
 */

#include <linux/types.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel_stat.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/time.h>
#include <linux/timex.h>
#include <linux/mc146818rtc.h>

#include <asm/mipsregs.h>
#include <asm/ptrace.h>
#include <asm/div64.h>
#include <asm/cpu.h>
#include <asm/time.h>
#include <asm/mc146818-time.h>

#include <asm/mips-boards/generic.h>
#include <asm/mips-boards/prom.h>
#if defined(CONFIG_MIPS_OHIO)
#include <asm/mips-boards/ohio.h>
#include <asm/mips-boards/ohioint.h>
#include <asm/mach_avm.h>
#endif /*--- #if defined(CONFIG_MIPS_OHIO) ---*/

unsigned long cpu_khz;

#if defined(CONFIG_MIPS_SEAD) || defined(CONFIG_MIPS_OHIO)
#define ALLINTS (IE_IRQ0 | IE_IRQ1 | IE_IRQ5)
#else
#define ALLINTS (IE_IRQ0 | IE_IRQ1 | IE_IRQ2 | IE_IRQ3 | IE_IRQ4 | IE_IRQ5)
#endif

#if defined(CONFIG_MIPS_ATLAS)
static char display_string[] = "        LINUX ON ATLAS       ";
#endif
#if defined(CONFIG_MIPS_MALTA)
static char display_string[] = "        LINUX ON MALTA       ";
#endif
#if defined(CONFIG_MIPS_SEAD)
static char display_string[] = "        LINUX ON SEAD       ";
#endif
#if defined(CONFIG_MIPS_OHIO)
static char display_string[] = "        LINUX ON OHIO       ";
#endif
static unsigned int display_count = 0;
#define MAX_DISPLAY_COUNT (sizeof(display_string) - 8)

#define MIPS_CPU_TIMER_IRQ (NR_IRQS-1)

static unsigned int timer_tick_count=0;

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if defined(CONFIG_MIPS_OHIO_SIMPLE_PROFILING) && CONFIG_MIPS_OHIO_SIMPLE_PROFILING > 0
#include <linux/avm_profile.h>
#include <linux/skbuff.h>
struct _simple_profiling {
    struct _avm_profile_data data[HZ * 60 * 2];
    atomic_t pos;
    unsigned int len;
    unsigned int enabled;
} simple_profiling;

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void ohio_simple_profiling(struct pt_regs *regs) {
    unsigned int pos, epc;
    if(simple_profiling.enabled == 0) return;
    epc = regs->cp0_epc;

    switch(epc & 0xE0000000) {
        case KERNEL_PERFOMANCE_USR_TRACE:
        case KERNEL_PERFOMANCE_TRACE_SHARE:
        case KERNEL_PERFOMANCE_TRACE_KERNEL:
        case KERNEL_PERFOMANCE_TRACE_VM:
            pos = atomic_inc_return(&(simple_profiling.pos)) - 1;
            if(pos >= simple_profiling.len - 1) {
                simple_profiling.enabled = 0;
                printk("[simple_profiling] buffer full: %u entries recorded\n", simple_profiling.len);
            }
            simple_profiling.data[pos].addr = epc;
            simple_profiling.data[pos].time = read_c0_count();
            break;
        default:
            break;
    }
    return;
}
EXPORT_SYMBOL(ohio_simple_profiling);

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void ohio_simple_profiling_log(unsigned int addr, unsigned int id) {
    unsigned int pos;
    struct sk_buff *skb;
    if(simple_profiling.enabled == 0) return;
    pos = atomic_inc_return(&(simple_profiling.pos)) - 1;
    if(pos >= simple_profiling.len - 1) {
        simple_profiling.enabled = 0;
        printk("[simple_profiling] buffer full: %u entries recorded\n", simple_profiling.len);
    }
    switch(id & 0xE0000000) {
        case KERNEL_PERFOMANCE_TRACE_SKB:
            skb = (struct sk_buff *)addr;
            simple_profiling.data[pos].addr = ((skb->uniq_id) & ~0xFF800000) | id;
            simple_profiling.data[pos].time = read_c0_count();
            return;
        case KERNEL_PERFOMANCE_TRACE_SHARE:
        case KERNEL_PERFOMANCE_TRACE_KERNEL:
        case KERNEL_PERFOMANCE_TRACE_VM:
        case KERNEL_PERFOMANCE_USR_TRACE:
            break;
        case KERNEL_PERFOMANCE_TRACE_RESERVED1:
        case KERNEL_PERFOMANCE_TRACE_RESERVED2:
        case KERNEL_PERFOMANCE_TRACE_RESERVED3:
            break;
    }
}
EXPORT_SYMBOL(ohio_simple_profiling_log);

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if defined(CPU_PROFILE_LOG)
void ohio_simple_profiling_text(const  char *text) {
    unsigned int pos;
    if(simple_profiling.enabled == 0) return;
    pos = atomic_inc_return(&(simple_profiling.pos)) - 1;
    if(pos >= simple_profiling.len - 1) {
        simple_profiling.enabled = 0;
        printk("[simple_profiling] buffer full: %u entries recorded\n", simple_profiling.len);
    }
    simple_profiling.data[pos].addr = ((unsigned int)text & ~0xE0000000) | KERNEL_TIMELOG_STRING;
    simple_profiling.data[pos].time = read_c0_count();
    return;
}
EXPORT_SYMBOL(ohio_simple_profiling_text);
#endif /*--- #if defined(CPU_PROFILE_LOG) ---*/

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
struct _avm_profile_data *ohio_simple_profiling_enable(unsigned int on, unsigned int *count) {
    if(on) {
        simple_profiling.enabled = 1;
        atomic_set(&(simple_profiling.pos), 0);
        if(count) *count = 0;
        return NULL;
    }
    simple_profiling.enabled = 0;
    if(count) *count = simple_profiling.pos.counter;
    printk("[simple_profiling] off: %u entries\n", simple_profiling.pos.counter);
    return simple_profiling.data;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void ohio_simple_profiling_init(void) {
    simple_profiling.len = HZ * 60 * CONFIG_MIPS_OHIO_SIMPLE_PROFILING;
    simple_profiling.enabled = 0;
    atomic_set(&(simple_profiling.pos), 0);
    printk("[simple_profiling] %u entries %u min\n", simple_profiling.len, CONFIG_MIPS_OHIO_SIMPLE_PROFILING);
}

module_init(ohio_simple_profiling_init);
#endif /*--- #if defined(CONFIG_MIPS_OHIO_SIMPLE_PROFILING) && CONFIG_MIPS_OHIO_SIMPLE_PROFILING > 0 ---*/

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void mips_timer_interrupt(struct pt_regs *regs)
{
#if defined(CONFIG_MIPS_OHIO_SIMPLE_PROFILING) && CONFIG_MIPS_OHIO_SIMPLE_PROFILING > 1
    ohio_simple_profiling(regs);
#endif /*--- #if defined(CONFIG_MIPS_OHIO_SIMPLE_PROFILING) && CONFIG_MIPS_OHIO_SIMPLE_PROFILING > 0 ---*/
#if defined(CONFIG_MIPS_OHIO)
	kstat_this_cpu.irqs[MIPS_EXCEPTION_OFFSET - 7]++;
    ohio_timer_interrupt();
#endif /*--- #if defined(CONFIG_MIPS_OHIO) ---*/
	if ((timer_tick_count++ % HZ) == 0) {
		mips_display_message(&display_string[display_count++]);
		if (display_count == MAX_DISPLAY_COUNT)
		        display_count = 0;

	}
	ll_timer_interrupt(MIPS_CPU_TIMER_IRQ, regs);
}

/*
 * Estimate CPU frequency.  Sets mips_counter_frequency as a side-effect
 */
static unsigned int __init estimate_cpu_frequency(void)
{
	unsigned int count;

#ifdef CONFIG_MIPS_SEAD
	unsigned int prid = read_c0_prid() & 0xffff00;
	/*
	 * The SEAD board doesn't have a real time clock, so we can't
	 * really calculate the timer frequency
	 * For now we hardwire the SEAD board frequency to 12MHz.
	 */
	
	if ((prid == (PRID_COMP_MIPS | PRID_IMP_20KC)) ||
	    (prid == (PRID_COMP_MIPS | PRID_IMP_25KF)))
		count = 12000000;
	else
		count = 6000000;
#endif
#if defined(CONFIG_MIPS_ATLAS) || defined(CONFIG_MIPS_MALTA)
	unsigned int flags;

	local_irq_save(flags);

	/* Start counter exactly on falling edge of update flag */
	while (CMOS_READ(RTC_REG_A) & RTC_UIP);
	while (!(CMOS_READ(RTC_REG_A) & RTC_UIP));

	/* Start r4k counter. */
	write_c0_count(0);

	/* Read counter exactly on falling edge of update flag */
	while (CMOS_READ(RTC_REG_A) & RTC_UIP);
	while (!(CMOS_READ(RTC_REG_A) & RTC_UIP));

	count = read_c0_count();

	/* restore interrupts */
	local_irq_restore(flags);
#endif
#if defined(CONFIG_MIPS_OHIO) /* TODO */
#if defined(CONFIG_OHIO_CLOCK_SWITCH)
    {
    static unsigned int mips_time_cpu_frequenz_change(enum _avm_clock_id, unsigned int);
    count = avm_get_clock_notify(avm_clock_id_cpu, mips_time_cpu_frequenz_change);
    }
#else /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/
    count = avm_get_clock(avm_clock_id_cpu);
#endif /*--- #else ---*/ /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/
	mips_hpt_frequency = count / 2;
#endif

#if !defined(CONFIG_MIPS_OHIO) /* TODO */
	if ((prid != (PRID_COMP_MIPS | PRID_IMP_20KC)) &&
	    (prid != (PRID_COMP_MIPS | PRID_IMP_25KF)))
		count *= 2;

	mips_hpt_frequency = count;
#endif /*--- #if !defined(CONFIG_MIPS_OHIO) ---*/ /* TODO */

	count += 5000;    /* round */
	count -= count%10000;

	return count;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#if defined(CONFIG_OHIO_CLOCK_SWITCH)
static unsigned int mips_time_cpu_frequenz_change(enum _avm_clock_id clock_id, unsigned int new_clk) {
    printk("[mips_time_cpu_frequenz_change] change to %u Hz\n", new_clk);
    return 0;
}
#endif /*--- #if defined(CONFIG_OHIO_CLOCK_SWITCH) ---*/

#if defined(CONFIG_MIPS_RTC)
unsigned long __init mips_rtc_get_time(void)
{
	return mc146818_get_cmos_time();
}
#endif /*--- #if defined(CONFIG_MIPS_RTC) ---*/

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void __init mips_time_init(void)
{
	unsigned int est_freq, flags;

	local_irq_save(flags);

#if defined(CONFIG_MIPS_ATLAS) || defined(CONFIG_MIPS_MALTA)
        /* Set Data mode - binary. */
        CMOS_WRITE(CMOS_READ(RTC_CONTROL) | RTC_DM_BINARY, RTC_CONTROL);
#endif

	est_freq = estimate_cpu_frequency ();

	printk("CPU frequency %d.%02d MHz\n", est_freq/1000000, (est_freq%1000000)*100/1000000);

        cpu_khz = est_freq / 1000;

	local_irq_restore(flags);
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void __init mips_timer_setup(struct irqaction *irq)
{
    /*--- printk("[mips_timer_setup] 1\n"); ---*/
	/* we are using the cpu counter for timer interrupts */
	irq->handler = no_action;     /* we use our own handler */
    /*--- printk("[mips_timer_setup] 2\n"); ---*/
	setup_irq(MIPS_CPU_TIMER_IRQ, irq);

    /*--- printk("[mips_timer_setup] 3\n"); ---*/
        /* to generate the first timer interrupt */
	write_c0_compare (read_c0_count() + mips_hpt_frequency/HZ);
    /*--- printk("[mips_timer_setup] 4\n"); ---*/
	set_c0_status(ALLINTS);
    /*--- printk("[mips_timer_setup] 5\n"); ---*/
}
