diff options
author | Juergen Beisert <j.beisert@pengutronix.de> | 2008-07-05 10:02:50 +0200 |
---|---|---|
committer | Robert Schwebel <r.schwebel@pengutronix.de> | 2008-07-05 10:02:50 +0200 |
commit | d0f349fbce2905607e0473d2358f97f48866e52c (patch) | |
tree | 7b6892554648c4ab2eae4b29a054787399a49094 /arch/arm | |
parent | 90292ea60f1c730efb9fea02b2e12676da89ebef (diff) |
i.MXC family: Adding timer support
This patch adds timer support for the i.MX machine family. This code can
be used on the following machs:
- i.MX1 (tested)
- i.MX2 (i.MX21 (to be tested), i.MX27 (tested))
- i.MX3 (i.MX31 (tested))
TODO: It seems impossible to build a kernel for more than one CPU because the
timer do not follow the platform device rules. So it does only work if
timer 1 can be accessed on all CPUs at the same address.
Signed-off-by: Juergen Beisert <j.beisert@pengutronix.de>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'arch/arm')
-rw-r--r-- | arch/arm/Kconfig | 2 | ||||
-rw-r--r-- | arch/arm/mach-mx3/Makefile | 2 | ||||
-rw-r--r-- | arch/arm/mach-mx3/mx31ads.c | 13 | ||||
-rw-r--r-- | arch/arm/mach-mx3/time.c | 148 | ||||
-rw-r--r-- | arch/arm/plat-mxc/Makefile | 2 | ||||
-rw-r--r-- | arch/arm/plat-mxc/time.c | 228 |
6 files changed, 244 insertions, 151 deletions
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 5c8c1a89be7..8a9a84c4adf 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -367,6 +367,8 @@ config ARCH_NS9XXX config ARCH_MXC bool "Freescale MXC/iMX-based" + select GENERIC_TIME + select GENERIC_CLOCKEVENTS select ARCH_MTD_XIP select GENERIC_GPIO select HAVE_GPIO_LIB diff --git a/arch/arm/mach-mx3/Makefile b/arch/arm/mach-mx3/Makefile index 68f062b70d3..562c75d2e37 100644 --- a/arch/arm/mach-mx3/Makefile +++ b/arch/arm/mach-mx3/Makefile @@ -4,5 +4,5 @@ # Object file lists. -obj-y := mm.o time.o clock.o devices.o iomux.o +obj-y := mm.o clock.o devices.o iomux.o obj-$(CONFIG_MACH_MX31ADS) += mx31ads.o diff --git a/arch/arm/mach-mx3/mx31ads.c b/arch/arm/mach-mx3/mx31ads.c index 5addbb7f711..eba3e0cd428 100644 --- a/arch/arm/mach-mx3/mx31ads.c +++ b/arch/arm/mach-mx3/mx31ads.c @@ -26,6 +26,7 @@ #include <asm/hardware.h> #include <asm/mach-types.h> #include <asm/mach/arch.h> +#include <asm/mach/time.h> #include <asm/memory.h> #include <asm/mach/map.h> #include <asm/arch/common.h> @@ -127,6 +128,16 @@ static void __init mxc_board_init(void) mxc_init_extuart(); } +static void __init mx31ads_timer_init(void) +{ + mxc_clocks_init(26000000); + mxc_timer_init("ipg_clk.0"); +} + +struct sys_timer mx31ads_timer = { + .init = mx31ads_timer_init, +}; + /* * The following uses standard kernel macros defined in arch.h in order to * initialize __mach_desc_MX31ADS data structure. @@ -139,5 +150,5 @@ MACHINE_START(MX31ADS, "Freescale MX31ADS") .map_io = mx31ads_map_io, .init_irq = mxc_init_irq, .init_machine = mxc_board_init, - .timer = &mxc_timer, + .timer = &mx31ads_timer, MACHINE_END diff --git a/arch/arm/mach-mx3/time.c b/arch/arm/mach-mx3/time.c deleted file mode 100644 index fb565c98dbf..00000000000 --- a/arch/arm/mach-mx3/time.c +++ /dev/null @@ -1,148 +0,0 @@ -/* - * System Timer Interrupt reconfigured to run in free-run mode. - * Author: Vitaly Wool - * Copyright 2004 MontaVista Software Inc. - * Copyright 2004-2007 Freescale Semiconductor, Inc. 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. - */ - -/*! - * @file time.c - * @brief This file contains OS tick and wdog timer implementations. - * - * This file contains OS tick and wdog timer implementations. - * - * @ingroup Timers - */ - -#include <linux/module.h> -#include <linux/init.h> -#include <linux/interrupt.h> -#include <linux/irq.h> -#include <asm/hardware.h> -#include <asm/mach/time.h> -#include <asm/io.h> -#include <asm/arch/common.h> - -/*! - * This is the timer interrupt service routine to do required tasks. - * It also services the WDOG timer at the frequency of twice per WDOG - * timeout value. For example, if the WDOG's timeout value is 4 (2 - * seconds since the WDOG runs at 0.5Hz), it will be serviced once - * every 2/2=1 second. - * - * @param irq GPT interrupt source number (not used) - * @param dev_id this parameter is not used - * @return always returns \b IRQ_HANDLED as defined in - * include/linux/interrupt.h. - */ -static irqreturn_t mxc_timer_interrupt(int irq, void *dev_id) -{ - unsigned int next_match; - - if (__raw_readl(MXC_GPT_GPTSR) & GPTSR_OF1) { - do { - timer_tick(); - next_match = __raw_readl(MXC_GPT_GPTOCR1) + LATCH; - __raw_writel(GPTSR_OF1, MXC_GPT_GPTSR); - __raw_writel(next_match, MXC_GPT_GPTOCR1); - } while ((signed long)(next_match - - __raw_readl(MXC_GPT_GPTCNT)) <= 0); - } - - return IRQ_HANDLED; -} - -/*! - * This function is used to obtain the number of microseconds since the last - * timer interrupt. Note that interrupts is disabled by do_gettimeofday(). - * - * @return the number of microseconds since the last timer interrupt. - */ -static unsigned long mxc_gettimeoffset(void) -{ - unsigned long ticks_to_match, elapsed, usec, tick_usec, i; - - /* Get ticks before next timer match */ - ticks_to_match = - __raw_readl(MXC_GPT_GPTOCR1) - __raw_readl(MXC_GPT_GPTCNT); - - /* We need elapsed ticks since last match */ - elapsed = LATCH - ticks_to_match; - - /* Now convert them to usec */ - /* Insure no overflow when calculating the usec below */ - for (i = 1, tick_usec = tick_nsec / 1000;; i *= 2) { - tick_usec /= i; - if ((0xFFFFFFFF / tick_usec) > elapsed) - break; - } - usec = (unsigned long)(elapsed * tick_usec) / (LATCH / i); - - return usec; -} - -/*! - * The OS tick timer interrupt structure. - */ -static struct irqaction timer_irq = { - .name = "MXC Timer Tick", - .flags = IRQF_DISABLED | IRQF_TIMER, - .handler = mxc_timer_interrupt -}; - -/*! - * This function is used to initialize the GPT to produce an interrupt - * based on HZ. It is called by start_kernel() during system startup. - */ -void __init mxc_init_time(void) -{ - u32 reg, v; - reg = __raw_readl(MXC_GPT_GPTCR); - reg &= ~GPTCR_ENABLE; - __raw_writel(reg, MXC_GPT_GPTCR); - reg |= GPTCR_SWR; - __raw_writel(reg, MXC_GPT_GPTCR); - - while ((__raw_readl(MXC_GPT_GPTCR) & GPTCR_SWR) != 0) - cpu_relax(); - - reg = GPTCR_FRR | GPTCR_CLKSRC_HIGHFREQ; - __raw_writel(reg, MXC_GPT_GPTCR); - - /* TODO: get timer rate from clk driver */ - v = 66500000; - - __raw_writel((v / CLOCK_TICK_RATE) - 1, MXC_GPT_GPTPR); - - if ((v % CLOCK_TICK_RATE) != 0) { - pr_info("\nWARNING: Can't generate CLOCK_TICK_RATE at %d Hz\n", - CLOCK_TICK_RATE); - } - pr_info("Actual CLOCK_TICK_RATE is %d Hz\n", - v / ((__raw_readl(MXC_GPT_GPTPR) & 0xFFF) + 1)); - - reg = __raw_readl(MXC_GPT_GPTCNT); - reg += LATCH; - __raw_writel(reg, MXC_GPT_GPTOCR1); - - setup_irq(MXC_INT_GPT, &timer_irq); - - reg = __raw_readl(MXC_GPT_GPTCR); - reg = - GPTCR_FRR | GPTCR_CLKSRC_HIGHFREQ | GPTCR_STOPEN | GPTCR_DOZEN | - GPTCR_WAITEN | GPTCR_ENMOD | GPTCR_ENABLE; - __raw_writel(reg, MXC_GPT_GPTCR); - - __raw_writel(GPTIR_OF1IE, MXC_GPT_GPTIR); -} - -struct sys_timer mxc_timer = { - .init = mxc_init_time, - .offset = mxc_gettimeoffset, -}; diff --git a/arch/arm/plat-mxc/Makefile b/arch/arm/plat-mxc/Makefile index 66272a6fc32..e3cc25c6d46 100644 --- a/arch/arm/plat-mxc/Makefile +++ b/arch/arm/plat-mxc/Makefile @@ -3,4 +3,4 @@ # # Common support -obj-y := irq.o clock.o gpio.o +obj-y := irq.o clock.o gpio.o time.o diff --git a/arch/arm/plat-mxc/time.c b/arch/arm/plat-mxc/time.c new file mode 100644 index 00000000000..3bf86343fdf --- /dev/null +++ b/arch/arm/plat-mxc/time.c @@ -0,0 +1,228 @@ +/* + * linux/arch/arm/plat-mxc/time.c + * + * Copyright (C) 2000-2001 Deep Blue Solutions + * Copyright (C) 2002 Shane Nay (shane@minirl.com) + * Copyright (C) 2006-2007 Pavel Pisa (ppisa@pikron.com) + * Copyright (C) 2008 Juergen Beisert (kernel@pengutronix.de) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that 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., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/clockchips.h> +#include <linux/clk.h> + +#include <asm/hardware.h> +#include <asm/mach/time.h> +#include <asm/arch/common.h> +#include <asm/arch/mxc_timer.h> + +static struct clock_event_device clockevent_mxc; +static enum clock_event_mode clockevent_mode = CLOCK_EVT_MODE_UNUSED; + +/* clock source for the timer */ +static struct clk *timer_clk; + +/* clock source */ + +static cycle_t mxc_get_cycles(void) +{ + return __raw_readl(TIMER_BASE + MXC_TCN); +} + +static struct clocksource clocksource_mxc = { + .name = "mxc_timer1", + .rating = 200, + .read = mxc_get_cycles, + .mask = CLOCKSOURCE_MASK(32), + .shift = 20, + .flags = CLOCK_SOURCE_IS_CONTINUOUS, +}; + +static int __init mxc_clocksource_init(void) +{ + unsigned int clock; + + clock = clk_get_rate(timer_clk); + + clocksource_mxc.mult = clocksource_hz2mult(clock, + clocksource_mxc.shift); + clocksource_register(&clocksource_mxc); + + return 0; +} + +/* clock event */ + +static int mxc_set_next_event(unsigned long evt, + struct clock_event_device *unused) +{ + unsigned long tcmp; + + tcmp = __raw_readl(TIMER_BASE + MXC_TCN) + evt; + __raw_writel(tcmp, TIMER_BASE + MXC_TCMP); + + return (int)(tcmp - __raw_readl(TIMER_BASE + MXC_TCN)) < 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 mxc_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 mxc_set_next_event() + */ + local_irq_save(flags); + + /* Disable interrupt in GPT module */ + gpt_irq_disable(); + + if (mode != clockevent_mode) { + /* Set event time into far-far future */ + __raw_writel(__raw_readl(TIMER_BASE + MXC_TCN) - 3, + TIMER_BASE + MXC_TCMP); + /* Clear pending interrupt */ + gpt_irq_acknowledge(); + } + +#ifdef DEBUG + printk(KERN_INFO "mxc_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"mxc_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 + * mxc_set_next_event(), the core has about 4 minutes + * to call mxc_set_next_event() or shutdown clock after + * mode switching + */ + local_irq_save(flags); + gpt_irq_enable(); + local_irq_restore(flags); + break; + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_RESUME: + /* Left event sources disabled, no more interrupts appear */ + break; + } +} + +/* + * IRQ handler for the timer + */ +static irqreturn_t mxc_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *evt = &clockevent_mxc; + uint32_t tstat; + + tstat = __raw_readl(TIMER_BASE + MXC_TSTAT); + + gpt_irq_acknowledge(); + + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction mxc_timer_irq = { + .name = "i.MX Timer Tick", + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, + .handler = mxc_timer_interrupt, +}; + +static struct clock_event_device clockevent_mxc = { + .name = "mxc_timer1", + .features = CLOCK_EVT_FEAT_ONESHOT, + .shift = 32, + .set_mode = mxc_set_mode, + .set_next_event = mxc_set_next_event, + .rating = 200, +}; + +static int __init mxc_clockevent_init(void) +{ + unsigned int clock; + + clock = clk_get_rate(timer_clk); + + clockevent_mxc.mult = div_sc(clock, NSEC_PER_SEC, + clockevent_mxc.shift); + clockevent_mxc.max_delta_ns = + clockevent_delta2ns(0xfffffffe, &clockevent_mxc); + clockevent_mxc.min_delta_ns = + clockevent_delta2ns(0xff, &clockevent_mxc); + + clockevent_mxc.cpumask = cpumask_of_cpu(0); + + clockevents_register_device(&clockevent_mxc); + + return 0; +} + +void __init mxc_timer_init(const char *clk_timer) +{ + timer_clk = clk_get(NULL, clk_timer); + if (!timer_clk) { + printk(KERN_ERR"Cannot determine timer clock. Giving up.\n"); + return; + } + + clk_enable(timer_clk); + + /* + * Initialise to a known state (all timers off, and timing reset) + */ + __raw_writel(0, TIMER_BASE + MXC_TCTL); + __raw_writel(0, TIMER_BASE + MXC_TPRER); /* see datasheet note */ + + __raw_writel(TCTL_FRR | /* free running */ + TCTL_VAL | /* set clocksource and arch specific bits */ + TCTL_TEN, /* start the timer */ + TIMER_BASE + MXC_TCTL); + + /* init and register the timer to the framework */ + mxc_clocksource_init(); + mxc_clockevent_init(); + + /* Make irqs happen */ + setup_irq(TIMER_INTERRUPT, &mxc_timer_irq); +} + |