Skip to content

Commit

Permalink
[ARM] 4374/3: i.MX/MX1 clock event source
Browse files Browse the repository at this point in the history
Support clock event source based on i.MX general purpose
timer in free running timer mode.

Signed-off-by: Pavel Pisa <pisa@cmp.felk.cvut.cz>
Acked-by: Thomas Gleixner <tglx@linutronix.de>
Acked-by: Sascha Hauer <s.hauer@pengutronix.de>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
  • Loading branch information
Pavel Pisa authored and Russell King committed Jul 12, 2007
1 parent b3e6a50 commit 89bba43
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 14 deletions.
2 changes: 2 additions & 0 deletions arch/arm/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ config ARCH_H720X
config ARCH_IMX
bool "IMX"
select GENERIC_GPIO
select GENERIC_TIME
select GENERIC_CLOCKEVENTS
help
Support for Motorola's i.MX family of processors (MX1, MXL).

Expand Down
121 changes: 107 additions & 14 deletions arch/arm/mach-imx/time.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*
* Copyright (C) 2000-2001 Deep Blue Solutions
* Copyright (C) 2002 Shane Nay (shane@minirl.com)
* Copyright (C) 2006-2007 Pavel Pisa (ppisa@pikron.com)
*
* 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
Expand All @@ -15,6 +16,7 @@
#include <linux/irq.h>
#include <linux/time.h>
#include <linux/clocksource.h>
#include <linux/clockchips.h>

#include <asm/hardware.h>
#include <asm/io.h>
Expand All @@ -25,33 +27,29 @@
/* Use timer 1 as system timer */
#define TIMER_BASE IMX_TIM1_BASE

static unsigned long evt_diff;
static struct clock_event_device clockevent_imx;
static enum clock_event_mode clockevent_mode = CLOCK_EVT_MODE_UNUSED;

/*
* IRQ handler for the timer
*/
static irqreturn_t
imx_timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *evt = &clockevent_imx;
uint32_t tstat;
irqreturn_t ret = IRQ_NONE;

/* clear the interrupt */
tstat = IMX_TSTAT(TIMER_BASE);
IMX_TSTAT(TIMER_BASE) = 0;

if (tstat & TSTAT_COMP) {
do {

write_seqlock(&xtime_lock);
timer_tick();
write_sequnlock(&xtime_lock);
IMX_TCMP(TIMER_BASE) += evt_diff;

} while (unlikely((int32_t)(IMX_TCMP(TIMER_BASE)
- IMX_TCN(TIMER_BASE)) < 0));
evt->event_handler(evt);
ret = IRQ_HANDLED;
}

return IRQ_HANDLED;
return ret;
}

static struct irqaction imx_timer_irq = {
Expand All @@ -70,10 +68,8 @@ static void __init imx_timer_hardware_init(void)
*/
IMX_TCTL(TIMER_BASE) = 0;
IMX_TPRER(TIMER_BASE) = 0;
IMX_TCMP(TIMER_BASE) = LATCH - 1;

IMX_TCTL(TIMER_BASE) = TCTL_FRR | TCTL_CLK_PCLK1 | TCTL_IRQEN | TCTL_TEN;
evt_diff = LATCH;
IMX_TCTL(TIMER_BASE) = TCTL_FRR | TCTL_CLK_PCLK1 | TCTL_TEN;
}

cycle_t imx_get_cycles(void)
Expand All @@ -99,11 +95,108 @@ static int __init imx_clocksource_init(void)
return 0;
}

static int imx_set_next_event(unsigned long evt,
struct clock_event_device *unused)
{
unsigned long tcmp;

tcmp = IMX_TCN(TIMER_BASE) + evt;
IMX_TCMP(TIMER_BASE) = tcmp;

return (int32_t)(tcmp - IMX_TCN(TIMER_BASE)) < 0 ? -ETIME : 0;
}

#ifdef DEBUG
static const char *clock_event_mode_label[]={
[CLOCK_EVT_MODE_PERIODIC] = "CLOCK_EVT_MODE_PERIODIC",
[CLOCK_EVT_MODE_ONESHOT] = "CLOCK_EVT_MODE_ONESHOT",
[CLOCK_EVT_MODE_SHUTDOWN] = "CLOCK_EVT_MODE_SHUTDOWN",
[CLOCK_EVT_MODE_UNUSED] = "CLOCK_EVT_MODE_UNUSED"
};
#endif /*DEBUG*/

static void imx_set_mode(enum clock_event_mode mode, struct clock_event_device *evt)
{
unsigned long flags;

/*
* The timer interrupt generation is disabled at least
* for enough time to call imx_set_next_event()
*/
local_irq_save(flags);
/* Disable interrupt in GPT module */
IMX_TCTL(TIMER_BASE) &= ~TCTL_IRQEN;
if (mode != clockevent_mode) {
/* Set event time into far-far future */
IMX_TCMP(TIMER_BASE) = IMX_TCN(TIMER_BASE) - 3;
/* Clear pending interrupt */
IMX_TSTAT(TIMER_BASE) &= ~TSTAT_COMP;
}

#ifdef DEBUG
printk(KERN_INFO "imx_set_mode: changing mode from %s to %s\n",
clock_event_mode_label[clockevent_mode], clock_event_mode_label[mode]);
#endif /*DEBUG*/

/* Remember timer mode */
clockevent_mode = mode;
local_irq_restore(flags);

switch (mode) {
case CLOCK_EVT_MODE_PERIODIC:
printk(KERN_ERR "imx_set_mode: Periodic mode is not supported for i.MX\n");
break;
case CLOCK_EVT_MODE_ONESHOT:
/*
* Do not put overhead of interrupt enable/disable into
* imx_set_next_event(), the core has about 4 minutes
* to call imx_set_next_event() or shutdown clock after
* mode switching
*/
local_irq_save(flags);
IMX_TCTL(TIMER_BASE) |= TCTL_IRQEN;
local_irq_restore(flags);
break;
case CLOCK_EVT_MODE_SHUTDOWN:
case CLOCK_EVT_MODE_UNUSED:
/* Left event sources disabled, no more interrupts appears */
break;
}
}

static struct clock_event_device clockevent_imx = {
.name = "imx_timer1",
.features = CLOCK_EVT_FEAT_ONESHOT,
.shift = 32,
.set_mode = imx_set_mode,
.set_next_event = imx_set_next_event,
.rating = 200,
};

static int __init imx_clockevent_init(void)
{
clockevent_imx.mult = div_sc(imx_get_perclk1(), NSEC_PER_SEC,
clockevent_imx.shift);
clockevent_imx.max_delta_ns =
clockevent_delta2ns(0xfffffffe, &clockevent_imx);
clockevent_imx.min_delta_ns =
clockevent_delta2ns(0xf, &clockevent_imx);

clockevent_imx.cpumask = cpumask_of_cpu(0);

clockevents_register_device(&clockevent_imx);

return 0;
}


static void __init imx_timer_init(void)
{
imx_timer_hardware_init();
imx_clocksource_init();

imx_clockevent_init();

/*
* Make irqs happen for the system timer
*/
Expand Down

0 comments on commit 89bba43

Please sign in to comment.