Subject: [@num@/@total@] i.MX family: Adding timer support
From: Juergen Beisert <j.beisert@pengutronix.de>

This patch adds timer support for the i.MX machine family. This code can
be used on the following machs:

 - i.MX1 (to be tested)
 - i.MX2 (i.MX21 (to be tested), i.MX27 (tested))
 - i.MX3 (i.MX31 (to be tested))

TODO: Find a solution for the early clock management. Only when its called from
here, the serial consoles are working later on. That should not happen.

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>

---

 arch/arm/plat-mxc/Makefile           |    2 
 arch/arm/plat-mxc/time.c             |  231 +++++++++++++++++++++++++++++++++++
 include/asm-arm/arch-mxc/imx_timer.h |   59 ++++++++
 3 files changed, 291 insertions(+), 1 deletion(-)

Index: arch/arm/plat-mxc/time.c
===================================================================
--- /dev/null
+++ arch/arm/plat-mxc/time.c
@@ -0,0 +1,231 @@
+/*
+ *  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 <asm/hardware.h>
+#include <asm/mach/time.h>
+
+#include <asm/arch/common.h>
+#include <asm/arch/imx_timer.h>
+#include <linux/clk.h>
+
+static struct clock_event_device clockevent_imx;
+static enum clock_event_mode clockevent_mode = CLOCK_EVT_MODE_UNUSED;
+
+/* clock source for the timer */
+static struct clk *timer_1_clk;
+
+/* clock source */
+
+cycle_t imx_get_cycles(void)
+{
+	return __raw_readl(TIMER_BASE + IMX_TCN);
+}
+
+static struct clocksource clocksource_imx = {
+	.name 		= "imx_timer1",
+	.rating		= 200,
+	.read		= imx_get_cycles,
+	.mask		= CLOCKSOURCE_MASK(32),
+	.shift 		= 20,
+	.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
+};
+
+static int __init imx_clocksource_init(void)
+{
+	clocksource_imx.mult = clocksource_hz2mult(clk_get_rate(timer_1_clk),
+					clocksource_imx.shift);
+	clocksource_register(&clocksource_imx);
+
+	return 0;
+}
+
+/* clock event */
+
+static int imx_set_next_event(unsigned long evt,
+			      struct clock_event_device *unused)
+{
+	unsigned long tcmp;
+
+	tcmp = __raw_readl(TIMER_BASE + IMX_TCN) + evt;
+	__raw_writel(tcmp, TIMER_BASE + IMX_TCMP);
+
+	return (int32_t)(tcmp - __raw_readl(TIMER_BASE + IMX_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 imx_set_mode(enum clock_event_mode mode, struct clock_event_device *evt)
+{
+	unsigned long flags, tmp;
+
+	/*
+	* 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 */
+	tmp = __raw_readl(TIMER_BASE + IMX_TCTL);
+	__raw_writel(tmp &~ TCTL_IRQEN, TIMER_BASE + IMX_TCTL);
+
+	if (mode != clockevent_mode) {
+		/* Set event time into far-far future */
+		__raw_writel(__raw_readl(TIMER_BASE + IMX_TCN) - 3,
+				TIMER_BASE + IMX_TCMP);
+		/* Clear pending interrupt */
+		__raw_writel(__raw_readl(TIMER_BASE + IMX_TSTAT) | TSTAT_COMP,
+				TIMER_BASE + IMX_TSTAT);
+	}
+
+#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);
+		__raw_writel(__raw_readl(TIMER_BASE + IMX_TCTL) | TCTL_IRQEN,
+				TIMER_BASE + IMX_TCTL);
+		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 appears */
+		break;
+	}
+}
+
+/*
+ * 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;
+
+	tstat = __raw_readl(TIMER_BASE + IMX_TSTAT);
+	/* clear the interrupt */
+	__raw_writel(TSTAT_CAPT | TSTAT_COMP, TIMER_BASE + IMX_TSTAT);
+
+	if (tstat & TSTAT_COMP) {
+		evt->event_handler(evt);
+		ret = IRQ_HANDLED;
+	}
+
+	return ret;
+}
+
+static struct irqaction imx_timer_irq = {
+	.name		= "i.MX Timer Tick",
+	.flags		= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
+	.handler	= imx_timer_interrupt,
+};
+
+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(clk_get_rate(timer_1_clk), 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;
+}
+
+/* it would be nice if we could use the clock framework here too. But
+ * the device tree is not up and running yet!
+ */
+void __init imx_timer_init(void)
+{
+	/* not a nice place to do it, but we need the framework right NOW */
+	mxc_clocks_init();
+
+	timer_1_clk = clk_get(NULL, "per_clk.0");
+	if (!timer_1_clk) {
+		printk("Cannot determine timer clock. Giving up.\n");
+		return;
+	}
+
+	clk_enable(timer_1_clk);
+	/*
+	 * Initialise to a known state (all timers off, and timing reset)
+	 */
+	__raw_writel(0, TIMER_BASE + IMX_TCTL);
+	__raw_writel(0, TIMER_BASE + IMX_TPRER); /* see datasheet note */
+
+	__raw_writel(TCTL_FRR |	/* free running */
+		     TCTL_CLK_PCLK1 |	/* use the internal clock */
+		     TCTL_TEN,		/* start the timer */
+		     TIMER_BASE + IMX_TCTL);
+
+	/* register the timer to the framework */
+	imx_clocksource_init();
+	imx_clockevent_init();
+
+	/* Make irqs happen */
+	setup_irq(INT_GPT1, &imx_timer_irq);
+}
+
+struct sys_timer imx_timer = {
+	.init	= imx_timer_init,
+};
+
Index: arch/arm/plat-mxc/Makefile
===================================================================
--- arch/arm/plat-mxc/Makefile.orig
+++ arch/arm/plat-mxc/Makefile
@@ -3,6 +3,6 @@
 #
 
 # Common support
-obj-y := irq.o gpio.o clock.o
+obj-y := irq.o gpio.o clock.o time.o
 
 obj-$(CONFIG_ARCH_MX2) += dma_mx2.o
Index: include/asm-arm/arch-mxc/imx_timer.h
===================================================================
--- /dev/null
+++ include/asm-arm/arch-mxc/imx_timer.h
@@ -0,0 +1,59 @@
+/*
+ * imx_timer.h
+ *
+ * Copyright (C) 2008 Juergen Beisert (kernel@pengutronix.de)
+ *
+ * Platform independent (i.MX1, i.MX2, i.MX3) definition for timer handling.
+ *
+ * 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.
+ */
+
+#ifndef __PLAT_MXC_TIMER_H
+#define __PLAT_MXC_TIMER_H
+
+#include <asm/hardware.h>
+
+/* Use timer 1 as system timer */
+#define TIMER_BASE IO_ADDRESS(GPT1_BASE_ADDR)
+
+#define IMX_TCTL		0x0
+# define TCTL_SWR		(1<<15)
+# define TCTL_CC		(1<<10)
+# define TCTL_OM		(1<<9)
+# define TCTL_FRR		(1<<8)
+#  define TCTL_CAP_RIS		(1<<6)
+#  define TCTL_CAP_FAL		(2<<6)
+#  define TCTL_CAP_RIS_FAL	(3<<6)
+#  define TCTL_CAP_ENA		(1<<5)
+# define TCTL_IRQEN		(1<<4)
+#  define TCTL_CLK_PCLK1	(1<<1)
+#  define TCTL_CLK_PCLK1_4	(2<<1)
+#  define TCTL_CLK_TIN		(3<<1)
+#  define TCTL_CLK_32		(4<<1)
+# define TCTL_TEN		(1<<0)
+
+#define IMX_TPRER		0x04
+#define IMX_TCMP		0x08
+#define IMX_TCR			0x0C
+#define IMX_TCN			0x10
+#define IMX_TSTAT		0x14
+# define TSTAT_CAPT         (1<<1)
+# define TSTAT_COMP         (1<<0)
+
+/* TODO: find a better solution for early timer init */
+unsigned long __init clk_early_get_timer_rate(void);
+
+#define get_timer_clock_rate clk_early_get_timer_rate
+
+#endif
