/*
 * arch/arm/mach-cxd4115/localtimer.c
 *
 * MPCore Local timer driver
 *
 * Copyright 2007,2008,2009 Sony Corporation
 *
 * This code is based on arch/arm/mach-realview/localtimer.c
 */
/*
 *  linux/arch/arm/mach-realview/localtimer.c
 *
 *  Copyright (C) 2002 ARM Ltd.
 *  All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/smp.h>
#include <linux/jiffies.h>
#include <linux/seq_file.h>
#include <linux/irq.h>

#include <asm/mach/time.h>
#include <asm/hardware/arm_twd.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/arch/gic.h>
#include <asm/arch/time.h>
#include <asm/arch/localtimer.h>

#define TWD_BASE		VA_LOCALTIMER
#define MPCORE_TIMER_RATE	CXD4115_LOCALTIMER_CLK

/*
 * local_timer_ack: checks for a local timer interrupt.
 *
 * If a local timer interrupt has occured, acknowledge and return 1.
 * Otherwise, return 0.
 */
int local_timer_ack(void)
{
	if (__raw_readl(TWD_BASE + TWD_TIMER_INTSTAT)) {
		__raw_writel(1, TWD_BASE+ TWD_TIMER_INTSTAT);
		return 1;
	}

	return 0;
}

#ifdef CONFIG_SMP
void __cpuinit local_timer_setup(unsigned int cpu)
{
	unsigned int load, offset;

	writel(MPCORE_TIMER_RATE / HZ, TWD_BASE + TWD_TIMER_LOAD);
	writel(0x7,  TWD_BASE + TWD_TIMER_CONTROL);

	/*
	 * Now maneuver our local tick into the right part of the jiffy.
	 * Start by working out where within the tick our local timer
	 * interrupt should go.
	 */
	offset = ((MPCORE_TIMER_RATE / HZ) / (NR_CPUS + 1)) * (cpu + 1);

	/*
	 * gettimeoffset() will return a number of us since the last tick.
	 * Convert this number of us to a local timer tick count.
	 * Be careful of integer overflow whilst keeping maximum precision.
	 *
	 * with HZ=100 and 1MHz (fpga) ~ 1GHz processor:
	 * load = 1 ~ 10,000
	 * mpcore_timer_rate/10000 = 100 ~ 100,000
	 *
	 * so the multiply value will be less than 10^9 always.
	 */
	load = (system_timer->offset() * (MPCORE_TIMER_RATE / 10000)) / 100;

	/* Add on our offset to get the load value */
	load = (load + offset) % (MPCORE_TIMER_RATE / HZ);

	writel(load, TWD_BASE + TWD_TIMER_COUNTER);

	/* Make sure our local interrupt controller has this enabled */
	writel(1 << IRQ_LOCALTIMER, VA_GIC_DIST + GIC_DIST_ENABLE_SET);
}
#else /* CONFIG_SMP */
void local_timer_setup(void)
{
	writel(TIMER_RELOAD-1, TWD_BASE + TWD_TIMER_LOAD);
	writel(((CXD4115_LOCALTIMER_DIV-1)<<TWD_PRESCALER_SHIFT)|TWD_INTEN|TWD_AUTORELOAD|TWD_START,  TWD_BASE + TWD_TIMER_CONTROL);

	/* Make sure our local interrupt controller has this enabled */
	writel(1 << IRQ_LOCALTIMER, VA_GIC_DIST + GIC_DIST_ENABLE_SET);
}

void local_wdog_setup(void)
{
	writel(0xffffffff, TWD_BASE + TWD_WDOG_LOAD);
	writel(((CXD4115_WDOG_DIV-1)<<TWD_PRESCALER_SHIFT)|TWD_AUTORELOAD|TWD_START,  TWD_BASE + TWD_WDOG_CONTROL);
}
#endif /* CONFIG_SMP */

/*
 * take a local timer down
 */
void local_timer_stop(unsigned int cpu)
{
	writel(0, TWD_BASE + TWD_TIMER_CONTROL);
}

#if !defined(CONFIG_SMP)
asmlinkage void __exception do_local_timer(struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);
	extern irqreturn_t cxd4115_timer_interrupt(int, void *);

	if (local_timer_ack()) {
		irq_stat[0].local_timer_irqs++;
		cxd4115_timer_interrupt(0, NULL);
	}

	set_irq_regs(old_regs);
}

void show_local_irqs(struct seq_file *p)
{
	unsigned int cpu;

	seq_printf(p, "LOC: ");

	for_each_present_cpu(cpu)
		seq_printf(p, "%10u ", irq_stat[cpu].local_timer_irqs);

	seq_putc(p, '\n');
}
#endif /* !CONFIG_SMP */
