[PATCH 7/8] sh: Simple timer framework.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This builds on some of the clock framework code to support a simple
system timer interface.

Signed-off-by: Paul Mundt <[email protected]>

---

 arch/sh/kernel/timers/Makefile    |    8 +
 arch/sh/kernel/timers/timer-tmu.c |  229 ++++++++++++++++++++++++++++++++++++++
 arch/sh/kernel/timers/timer.c     |   50 ++++++++
 include/asm-sh/timer.h            |   42 ++++++
 4 files changed, 329 insertions(+)

diff -urN -X exclude linux-2.6.15/arch/sh/kernel/timers/Makefile sh-2.6.15/arch/sh/kernel/timers/Makefile
--- linux-2.6.15/arch/sh/kernel/timers/Makefile	1970-01-01 02:00:00.000000000 +0200
+++ sh-2.6.15/arch/sh/kernel/timers/Makefile	2006-01-04 00:15:28.000000000 +0200
@@ -0,0 +1,8 @@
+#
+# Makefile for the various Linux/SuperH timers
+#
+
+obj-y	:= timer.o
+
+obj-$(CONFIG_SH_TMU)		+= timer-tmu.o
+
diff -urN -X exclude linux-2.6.15/arch/sh/kernel/timers/timer-tmu.c sh-2.6.15/arch/sh/kernel/timers/timer-tmu.c
--- linux-2.6.15/arch/sh/kernel/timers/timer-tmu.c	1970-01-01 02:00:00.000000000 +0200
+++ sh-2.6.15/arch/sh/kernel/timers/timer-tmu.c	2006-01-04 00:15:28.000000000 +0200
@@ -0,0 +1,229 @@
+/*
+ * arch/sh/kernel/timers/timer-tmu.c - TMU Timer Support
+ *
+ *  Copyright (C) 2005  Paul Mundt
+ *
+ * TMU handling code hacked out of arch/sh/kernel/time.c
+ *
+ *  Copyright (C) 1999  Tetsuya Okada & Niibe Yutaka
+ *  Copyright (C) 2000  Philipp Rumpf <[email protected]>
+ *  Copyright (C) 2002, 2003, 2004  Paul Mundt
+ *  Copyright (C) 2002  M. R. Brown  <[email protected]>
+ *
+ * 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/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/seqlock.h>
+#include <asm/timer.h>
+#include <asm/rtc.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/clock.h>
+
+#define TMU_TOCR_INIT	0x00
+#define TMU0_TCR_INIT	0x0020
+#define TMU_TSTR_INIT	1
+
+#define TMU0_TCR_CALIB	0x0000
+
+static DEFINE_SPINLOCK(tmu0_lock);
+
+static unsigned long tmu_timer_get_offset(void)
+{
+	int count;
+	unsigned long flags;
+
+	static int count_p = 0x7fffffff;    /* for the first call after boot */
+	static unsigned long jiffies_p = 0;
+
+	/*
+	 * cache volatile jiffies temporarily; we have IRQs turned off.
+	 */
+	unsigned long jiffies_t;
+
+	spin_lock_irqsave(&tmu0_lock, flags);
+	/* timer count may underflow right here */
+	count = ctrl_inl(TMU0_TCNT);	/* read the latched count */
+
+	jiffies_t = jiffies;
+
+	/*
+	 * avoiding timer inconsistencies (they are rare, but they happen)...
+	 * there is one kind of problem that must be avoided here:
+	 *  1. the timer counter underflows
+	 */
+
+	if (jiffies_t == jiffies_p) {
+		if (count > count_p) {
+			/* the nutcase */
+			if (ctrl_inw(TMU0_TCR) & 0x100) { /* Check UNF bit */
+				count -= LATCH;
+			} else {
+				printk("%s (): hardware timer problem?\n",
+				       __FUNCTION__);
+			}
+		}
+	} else
+		jiffies_p = jiffies_t;
+
+	count_p = count;
+	spin_unlock_irqrestore(&tmu0_lock, flags);
+
+	count = ((LATCH-1) - count) * TICK_SIZE;
+	count = (count + LATCH/2) / LATCH;
+
+	return count;
+}
+
+static irqreturn_t tmu_timer_interrupt(int irq, void *dev_id,
+				       struct pt_regs *regs)
+{
+	unsigned long timer_status;
+
+	/* Clear UNF bit */
+	timer_status = ctrl_inw(TMU0_TCR);
+	timer_status &= ~0x100;
+	ctrl_outw(timer_status, TMU0_TCR);
+
+	/*
+	 * Here we are in the timer irq handler. We just have irqs locally
+	 * disabled but we don't know if the timer_bh is running on the other
+	 * CPU. We need to avoid to SMP race with it. NOTE: we don' t need
+	 * the irq version of write_lock because as just said we have irq
+	 * locally disabled. -arca
+	 */
+	write_seqlock(&xtime_lock);
+	handle_timer_tick(regs);
+	write_sequnlock(&xtime_lock);
+
+	return IRQ_HANDLED;
+}
+
+static struct irqaction tmu_irq = {
+	.name		= "timer",
+	.handler	= tmu_timer_interrupt,
+	.flags		= SA_INTERRUPT,
+	.mask		= CPU_MASK_NONE,
+};
+
+/*
+ * Hah!  We'll see if this works (switching from usecs to nsecs).
+ */
+static unsigned long tmu_timer_get_frequency(void)
+{
+	u32 freq;
+	struct timespec ts1, ts2;
+	unsigned long diff_nsec;
+	unsigned long factor;
+
+	/* Setup the timer:  We don't want to generate interrupts, just
+	 * have it count down at its natural rate.
+	 */
+	ctrl_outb(0, TMU_TSTR);
+#if !defined(CONFIG_CPU_SUBTYPE_SH7300) && !defined(CONFIG_CPU_SUBTYPE_SH7760)
+	ctrl_outb(TMU_TOCR_INIT, TMU_TOCR);
+#endif
+	ctrl_outw(TMU0_TCR_CALIB, TMU0_TCR);
+	ctrl_outl(0xffffffff, TMU0_TCOR);
+	ctrl_outl(0xffffffff, TMU0_TCNT);
+
+	rtc_get_time(&ts2);
+
+	do {
+		rtc_get_time(&ts1);
+	} while (ts1.tv_nsec == ts2.tv_nsec && ts1.tv_sec == ts2.tv_sec);
+
+	/* actually start the timer */
+	ctrl_outb(TMU_TSTR_INIT, TMU_TSTR);
+
+	do {
+		rtc_get_time(&ts2);
+	} while (ts1.tv_nsec == ts2.tv_nsec && ts1.tv_sec == ts2.tv_sec);
+
+	freq = 0xffffffff - ctrl_inl(TMU0_TCNT);
+	if (ts2.tv_nsec < ts1.tv_nsec) {
+		ts2.tv_nsec += 1000000000;
+		ts2.tv_sec--;
+	}
+
+	diff_nsec = (ts2.tv_sec - ts1.tv_sec) * 1000000000 + (ts2.tv_nsec - ts1.tv_nsec);
+
+	/* this should work well if the RTC has a precision of n Hz, where
+	 * n is an integer.  I don't think we have to worry about the other
+	 * cases. */
+	factor = (1000000000 + diff_nsec/2) / diff_nsec;
+
+	if (factor * diff_nsec > 1100000000 ||
+	    factor * diff_nsec <  900000000)
+		panic("weird RTC (diff_nsec %ld)", diff_nsec);
+
+	return freq * factor;
+}
+
+static void tmu_clk_init(struct clk *clk)
+{
+	u8 divisor = TMU0_TCR_INIT & 0x7;
+	ctrl_outw(TMU0_TCR_INIT, TMU0_TCR);
+	clk->rate = clk->parent->rate / (4 << (divisor << 1));
+}
+
+static void tmu_clk_recalc(struct clk *clk)
+{
+	u8 divisor = ctrl_inw(TMU0_TCR) & 0x7;
+	clk->rate = clk->parent->rate / (4 << (divisor << 1));
+}
+
+static struct clk_ops tmu_clk_ops = {
+	.init		= tmu_clk_init,
+	.recalc		= tmu_clk_recalc,
+};
+
+static struct clk tmu0_clk = {
+	.name		= "tmu0_clk",
+	.ops		= &tmu_clk_ops,
+};
+
+static int tmu_timer_init(void)
+{
+	unsigned long interval;
+
+	setup_irq(TIMER_IRQ, &tmu_irq);
+
+	tmu0_clk.parent = clk_get("module_clk");
+
+	/* Start TMU0 */
+	ctrl_outb(0, TMU_TSTR);
+#if !defined(CONFIG_CPU_SUBTYPE_SH7300) && !defined(CONFIG_CPU_SUBTYPE_SH7760)
+	ctrl_outb(TMU_TOCR_INIT, TMU_TOCR);
+#endif
+
+	clk_register(&tmu0_clk);
+	clk_enable(&tmu0_clk);
+
+	interval = (clk_get_rate(&tmu0_clk) + HZ / 2) / HZ;
+	printk(KERN_INFO "Interval = %ld\n", interval);
+
+	ctrl_outl(interval, TMU0_TCOR);
+	ctrl_outl(interval, TMU0_TCNT);
+
+	ctrl_outb(TMU_TSTR_INIT, TMU_TSTR);
+
+	return 0;
+}
+
+struct sys_timer_ops tmu_timer_ops = {
+	.init		= tmu_timer_init,
+	.get_frequency	= tmu_timer_get_frequency,
+	.get_offset	= tmu_timer_get_offset,
+};
+
+struct sys_timer tmu_timer = {
+	.name	= "tmu",
+	.ops	= &tmu_timer_ops,
+};
+
diff -urN -X exclude linux-2.6.15/arch/sh/kernel/timers/timer.c sh-2.6.15/arch/sh/kernel/timers/timer.c
--- linux-2.6.15/arch/sh/kernel/timers/timer.c	1970-01-01 02:00:00.000000000 +0200
+++ sh-2.6.15/arch/sh/kernel/timers/timer.c	2006-01-04 00:15:28.000000000 +0200
@@ -0,0 +1,50 @@
+/*
+ * arch/sh/kernel/timers/timer.c - Common timer code
+ *
+ *  Copyright (C) 2005  Paul Mundt
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <asm/timer.h>
+
+static struct sys_timer *sys_timers[] __initdata = {
+#ifdef CONFIG_SH_TMU
+	&tmu_timer,
+#endif
+	NULL,
+};
+
+static char timer_override[10] __initdata;
+static int __init timer_setup(char *str)
+{
+	if (str)
+		strlcpy(timer_override, str, sizeof(timer_override));
+	return 1;
+}
+__setup("timer=", timer_setup);
+
+struct sys_timer *get_sys_timer(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(sys_timers); i++) {
+		struct sys_timer *t = sys_timers[i];
+
+		if (unlikely(!t))
+			break;
+		if (unlikely(timer_override[0]))
+			if ((strcmp(timer_override, t->name) != 0))
+				continue;
+		if (likely(t->ops->init() == 0))
+			return t;
+	}
+
+	return NULL;
+}
+

diff -urN -X exclude linux-2.6.15/include/asm-sh/timer.h sh-2.6.15/include/asm-sh/timer.h
--- linux-2.6.15/include/asm-sh/timer.h	1970-01-01 02:00:00.000000000 +0200
+++ sh-2.6.15/include/asm-sh/timer.h	2006-01-04 00:15:30.000000000 +0200
@@ -0,0 +1,42 @@
+#ifndef __ASM_SH_TIMER_H
+#define __ASM_SH_TIMER_H
+
+#include <linux/sysdev.h>
+#include <asm/cpu/timer.h>
+
+struct sys_timer_ops {
+	int (*init)(void);
+	unsigned long (*get_offset)(void);
+	unsigned long (*get_frequency)(void);
+};
+
+struct sys_timer {
+	const char		*name;
+
+	struct sys_device	dev;
+	struct sys_timer_ops	*ops;
+};
+
+#define TICK_SIZE (tick_nsec / 1000)
+
+extern struct sys_timer tmu_timer;
+extern struct sys_timer *sys_timer;
+
+static inline unsigned long get_timer_offset(void)
+{
+	return sys_timer->ops->get_offset();
+}
+
+static inline unsigned long get_timer_frequency(void)
+{
+	return sys_timer->ops->get_frequency();
+}
+
+/* arch/sh/kernel/timers/timer.c */
+struct sys_timer *get_sys_timer(void);
+
+/* arch/sh/kernel/time.c */
+void handle_timer_tick(struct pt_regs *);
+
+#endif /* __ASM_SH_TIMER_H */
+
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux