/*
 * arch/arm/mach-cxd4105/time.c
 *
 * timer driver
 *
 * Copyright 2007 Sony Corporation
 *
 * This code is based on arch/arm/mach-integrator/core.c.
 */
/*
 *  linux/arch/arm/mach-integrator/core.c
 *
 *  Copyright (C) 2000-2003 Deep Blue Solutions Ltd
 *
 * 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/device.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/pm.h>

#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/irq.h>

#include <asm/arch/time.h>
#include <asm/mach/time.h>
#include <asm/arch/regs-clk.h>

/* sched clock */
unsigned long long sched_clock(void)
{
	unsigned long long v;

	v = (unsigned long long)readl(TIMER_VA_BASE(TIMER_FOR_SCHED)+CXD4105_TIMERREAD);
	return ticks2ns(v);
}

unsigned long long read_sched_clock(void)
{
	unsigned long long v;

	v = (unsigned long long)readl(TIMER_VA_BASE(TIMER_FOR_SCHED)+CXD4105_TIMERREAD);
	return ticks2ns(v);
}
EXPORT_SYMBOL(read_sched_clock);

/*
 * Returns number of ms since last clock interrupt.  Note that interrupts
 * will have been disabled by do_gettimeoffset()
 */
static unsigned long cxd4105_gettimeoffset(void)
{
	unsigned long ticks1;

	/* If TimerRead counter reaches TimerCompare value
	 * the counter will be cleared, so that just read TimerRead value.
	 */

	ticks1 = readl(TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERREAD);
	if (readl(INT_CTRL(TIMER_STATUS)) & (1 << TIMER_FOR_TICK)) {
		if (ticks1 < TIMER_RELOAD-1)
			ticks1 += TIMER_RELOAD;
	}
	return TICKS2USECS(ticks1);
}

/*
 * IRQ handler for the timer
 */
static irqreturn_t cxd4105_timer_interrupt(int irq, void *dev_id)
{
	write_seqlock(&xtime_lock);

	// ...clear the interrupt
	// Counter automatically cleared in periodic mode.
	writel(1, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCLR);

	timer_tick();

	write_sequnlock(&xtime_lock);

	return IRQ_HANDLED;
}

static struct irqaction cxd4105_timer_irq = {
	.name		= "CXD4105 timer",
	.flags		= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
	.handler	= cxd4105_timer_interrupt
};

/*
 * Setup sched clock(profile timer)
 * 	Called when resume (cxd4105_pm_enter()).
 */
void cxd4105_timer_early_init(void)
{
	// clock enable
	writeb(1, VA_CLKBLK_BASE + CLKBLK_PSCKTIM);

	/* Clear the sched clock */
	writel(0, TIMER_VA_BASE(TIMER_FOR_SCHED)+CXD4105_TIMERCTL);
	writel(TMCLR|TMINTCLR, TIMER_VA_BASE(TIMER_FOR_SCHED)+CXD4105_TIMERCLR);
	/* Start the sched clock */
	writel(TMCK_DIV1|TMCS_FREERUN|TMST, TIMER_VA_BASE(TIMER_FOR_SCHED)+CXD4105_TIMERCTL);
}

static void __init cxd4105_init_timer(void)
{
	/* Clear */
	writel(0, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCTL);
	writel(TMCLR|TMINTCLR, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCLR);

	/* Setup the system timer */
	writel(TMCK_DIV1|TMCS_PERIODIC|TMINT, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCTL);
	writel(TIMER_RELOAD, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCMP);

	setup_irq(IRQ_TIMERx(TIMER_FOR_TICK), &cxd4105_timer_irq);

	/* Start the system timer */
	writel(TMCK_DIV1|TMCS_PERIODIC|TMINT|TMST, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCTL);

#ifdef CONFIG_CXD4105_PRINTK_SCHED_CLOCK
	/* This should be done after map_io. (paging_init) */
	safe_to_call_sched_clock = 1;
#endif /* CONFIG_CXD4105_PRINTK_SCHED_CLOCK */
}

#ifdef CONFIG_PM
static void suspend_timer(void)
{
	/* Stop the system timer */
	disable_irq(IRQ_TIMERx(TIMER_FOR_TICK));
	writel(0, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCTL);
	writel(TMINTCLR, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCLR);
}

static void resume_timer(void)
{
	/* Clear */
	writel(0, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCTL);
	writel(TMCLR|TMINTCLR, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCLR);

	/* Setup the system timer */
	writel(TMCK_DIV1|TMCS_PERIODIC|TMINT, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCTL);
	writel(TIMER_RELOAD, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCMP);

	/* Start the system timer */
	writel(TMCK_DIV1|TMCS_PERIODIC|TMINT|TMST, TIMER_VA_BASE(TIMER_FOR_TICK)+CXD4105_TIMERCTL);

	enable_irq(IRQ_TIMERx(TIMER_FOR_TICK));
}
#endif /* CONFIG_PM */

struct sys_timer cxd4105_timer __initdata_refok = {
	.init		= cxd4105_init_timer,
	.offset		= cxd4105_gettimeoffset,
#ifdef CONFIG_PM
	.suspend	= suspend_timer,
	.resume		= resume_timer,
#endif
};
