38698bef54
Set up the GPTIMER hwmod used for the clockevent source immediately before it is used. This avoids the need to set up all of the hwmods until the boot process is further along. (In general, we want to defer as much as possible until late in the boot process.) This second version fixes a bug pointed out by Santosh Shilimkar <santosh.shilimkar@ti.com>, that would cause the kernel to use an incorrect timer hwmod name if the selected GPTIMER was not 1 or 12 - thanks Santosh. Also, Tarun Kanti DebBarma <tarun.kanti@ti.com> pointed out that the original patch did not apply cleanly; this has now been fixed. Signed-off-by: Paul Walmsley <paul@pwsan.com> Cc: Benoît Cousson <b-cousson@ti.com> Cc: Tony Lindgren <tony@atomide.com> Cc: Kevin Hilman <khilman@ti.com> Cc: Santosh Shilimkar <santosh.shilimkar@ti.com> Acked-by: Santosh Shilimkar <santosh.shilimkar@ti.com> Cc: Tarun Kanti DebBarma <tarun.kanti@ti.com>
254 lines
6.5 KiB
C
254 lines
6.5 KiB
C
/*
|
|
* linux/arch/arm/mach-omap2/timer-gp.c
|
|
*
|
|
* OMAP2 GP timer support.
|
|
*
|
|
* Copyright (C) 2009 Nokia Corporation
|
|
*
|
|
* Update to use new clocksource/clockevent layers
|
|
* Author: Kevin Hilman, MontaVista Software, Inc. <source@mvista.com>
|
|
* Copyright (C) 2007 MontaVista Software, Inc.
|
|
*
|
|
* Original driver:
|
|
* Copyright (C) 2005 Nokia Corporation
|
|
* Author: Paul Mundt <paul.mundt@nokia.com>
|
|
* Juha Yrjölä <juha.yrjola@nokia.com>
|
|
* OMAP Dual-mode timer framework support by Timo Teras
|
|
*
|
|
* Some parts based off of TI's 24xx code:
|
|
*
|
|
* Copyright (C) 2004-2009 Texas Instruments, Inc.
|
|
*
|
|
* Roughly modelled after the OMAP1 MPU timer code.
|
|
* Added OMAP4 support - Santosh Shilimkar <santosh.shilimkar@ti.com>
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file "COPYING" in the main directory of this archive
|
|
* for more details.
|
|
*/
|
|
#include <linux/init.h>
|
|
#include <linux/time.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/err.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/clocksource.h>
|
|
#include <linux/clockchips.h>
|
|
|
|
#include <asm/mach/time.h>
|
|
#include <plat/dmtimer.h>
|
|
#include <asm/localtimer.h>
|
|
#include <plat/common.h>
|
|
#include <plat/omap_hwmod.h>
|
|
|
|
#include "timer-gp.h"
|
|
|
|
|
|
/* MAX_GPTIMER_ID: number of GPTIMERs on the chip */
|
|
#define MAX_GPTIMER_ID 12
|
|
|
|
static struct omap_dm_timer *gptimer;
|
|
static struct clock_event_device clockevent_gpt;
|
|
static u8 __initdata gptimer_id = 1;
|
|
static u8 __initdata inited;
|
|
struct omap_dm_timer *gptimer_wakeup;
|
|
|
|
static irqreturn_t omap2_gp_timer_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct omap_dm_timer *gpt = (struct omap_dm_timer *)dev_id;
|
|
struct clock_event_device *evt = &clockevent_gpt;
|
|
|
|
omap_dm_timer_write_status(gpt, OMAP_TIMER_INT_OVERFLOW);
|
|
|
|
evt->event_handler(evt);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct irqaction omap2_gp_timer_irq = {
|
|
.name = "gp timer",
|
|
.flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
|
|
.handler = omap2_gp_timer_interrupt,
|
|
};
|
|
|
|
static int omap2_gp_timer_set_next_event(unsigned long cycles,
|
|
struct clock_event_device *evt)
|
|
{
|
|
omap_dm_timer_set_load_start(gptimer, 0, 0xffffffff - cycles);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void omap2_gp_timer_set_mode(enum clock_event_mode mode,
|
|
struct clock_event_device *evt)
|
|
{
|
|
u32 period;
|
|
|
|
omap_dm_timer_stop(gptimer);
|
|
|
|
switch (mode) {
|
|
case CLOCK_EVT_MODE_PERIODIC:
|
|
period = clk_get_rate(omap_dm_timer_get_fclk(gptimer)) / HZ;
|
|
period -= 1;
|
|
omap_dm_timer_set_load_start(gptimer, 1, 0xffffffff - period);
|
|
break;
|
|
case CLOCK_EVT_MODE_ONESHOT:
|
|
break;
|
|
case CLOCK_EVT_MODE_UNUSED:
|
|
case CLOCK_EVT_MODE_SHUTDOWN:
|
|
case CLOCK_EVT_MODE_RESUME:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static struct clock_event_device clockevent_gpt = {
|
|
.name = "gp timer",
|
|
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
|
|
.shift = 32,
|
|
.set_next_event = omap2_gp_timer_set_next_event,
|
|
.set_mode = omap2_gp_timer_set_mode,
|
|
};
|
|
|
|
/**
|
|
* omap2_gp_clockevent_set_gptimer - set which GPTIMER is used for clockevents
|
|
* @id: GPTIMER to use (1..MAX_GPTIMER_ID)
|
|
*
|
|
* Define the GPTIMER that the system should use for the tick timer.
|
|
* Meant to be called from board-*.c files in the event that GPTIMER1, the
|
|
* default, is unsuitable. Returns -EINVAL on error or 0 on success.
|
|
*/
|
|
int __init omap2_gp_clockevent_set_gptimer(u8 id)
|
|
{
|
|
if (id < 1 || id > MAX_GPTIMER_ID)
|
|
return -EINVAL;
|
|
|
|
BUG_ON(inited);
|
|
|
|
gptimer_id = id;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __init omap2_gp_clockevent_init(void)
|
|
{
|
|
u32 tick_rate;
|
|
int src;
|
|
char clockevent_hwmod_name[8]; /* 8 = sizeof("timerXX0") */
|
|
|
|
inited = 1;
|
|
|
|
sprintf(clockevent_hwmod_name, "timer%d", gptimer_id);
|
|
omap_hwmod_setup_one(clockevent_hwmod_name);
|
|
|
|
gptimer = omap_dm_timer_request_specific(gptimer_id);
|
|
BUG_ON(gptimer == NULL);
|
|
gptimer_wakeup = gptimer;
|
|
|
|
#if defined(CONFIG_OMAP_32K_TIMER)
|
|
src = OMAP_TIMER_SRC_32_KHZ;
|
|
#else
|
|
src = OMAP_TIMER_SRC_SYS_CLK;
|
|
WARN(gptimer_id == 12, "WARNING: GPTIMER12 can only use the "
|
|
"secure 32KiHz clock source\n");
|
|
#endif
|
|
|
|
if (gptimer_id != 12)
|
|
WARN(IS_ERR_VALUE(omap_dm_timer_set_source(gptimer, src)),
|
|
"timer-gp: omap_dm_timer_set_source() failed\n");
|
|
|
|
tick_rate = clk_get_rate(omap_dm_timer_get_fclk(gptimer));
|
|
|
|
pr_info("OMAP clockevent source: GPTIMER%d at %u Hz\n",
|
|
gptimer_id, tick_rate);
|
|
|
|
omap2_gp_timer_irq.dev_id = (void *)gptimer;
|
|
setup_irq(omap_dm_timer_get_irq(gptimer), &omap2_gp_timer_irq);
|
|
omap_dm_timer_set_int_enable(gptimer, OMAP_TIMER_INT_OVERFLOW);
|
|
|
|
clockevent_gpt.mult = div_sc(tick_rate, NSEC_PER_SEC,
|
|
clockevent_gpt.shift);
|
|
clockevent_gpt.max_delta_ns =
|
|
clockevent_delta2ns(0xffffffff, &clockevent_gpt);
|
|
clockevent_gpt.min_delta_ns =
|
|
clockevent_delta2ns(3, &clockevent_gpt);
|
|
/* Timer internal resynch latency. */
|
|
|
|
clockevent_gpt.cpumask = cpumask_of(0);
|
|
clockevents_register_device(&clockevent_gpt);
|
|
}
|
|
|
|
/* Clocksource code */
|
|
|
|
#ifdef CONFIG_OMAP_32K_TIMER
|
|
/*
|
|
* When 32k-timer is enabled, don't use GPTimer for clocksource
|
|
* instead, just leave default clocksource which uses the 32k
|
|
* sync counter. See clocksource setup in plat-omap/counter_32k.c
|
|
*/
|
|
|
|
static void __init omap2_gp_clocksource_init(void)
|
|
{
|
|
omap_init_clocksource_32k();
|
|
}
|
|
|
|
#else
|
|
/*
|
|
* clocksource
|
|
*/
|
|
static struct omap_dm_timer *gpt_clocksource;
|
|
static cycle_t clocksource_read_cycles(struct clocksource *cs)
|
|
{
|
|
return (cycle_t)omap_dm_timer_read_counter(gpt_clocksource);
|
|
}
|
|
|
|
static struct clocksource clocksource_gpt = {
|
|
.name = "gp timer",
|
|
.rating = 300,
|
|
.read = clocksource_read_cycles,
|
|
.mask = CLOCKSOURCE_MASK(32),
|
|
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
|
};
|
|
|
|
/* Setup free-running counter for clocksource */
|
|
static void __init omap2_gp_clocksource_init(void)
|
|
{
|
|
static struct omap_dm_timer *gpt;
|
|
u32 tick_rate;
|
|
static char err1[] __initdata = KERN_ERR
|
|
"%s: failed to request dm-timer\n";
|
|
static char err2[] __initdata = KERN_ERR
|
|
"%s: can't register clocksource!\n";
|
|
|
|
gpt = omap_dm_timer_request();
|
|
if (!gpt)
|
|
printk(err1, clocksource_gpt.name);
|
|
gpt_clocksource = gpt;
|
|
|
|
omap_dm_timer_set_source(gpt, OMAP_TIMER_SRC_SYS_CLK);
|
|
tick_rate = clk_get_rate(omap_dm_timer_get_fclk(gpt));
|
|
|
|
omap_dm_timer_set_load_start(gpt, 1, 0);
|
|
|
|
if (clocksource_register_hz(&clocksource_gpt, tick_rate))
|
|
printk(err2, clocksource_gpt.name);
|
|
}
|
|
#endif
|
|
|
|
static void __init omap2_gp_timer_init(void)
|
|
{
|
|
#ifdef CONFIG_LOCAL_TIMERS
|
|
if (cpu_is_omap44xx()) {
|
|
twd_base = ioremap(OMAP44XX_LOCAL_TWD_BASE, SZ_256);
|
|
BUG_ON(!twd_base);
|
|
}
|
|
#endif
|
|
omap_dm_timer_init();
|
|
|
|
omap2_gp_clockevent_init();
|
|
omap2_gp_clocksource_init();
|
|
}
|
|
|
|
struct sys_timer omap_timer = {
|
|
.init = omap2_gp_timer_init,
|
|
};
|