[RFD] Clockevent support on MIPS

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

 



Hi all,

I sent this patch just for starting a discussion on the MIPS time
code rework.

Recently Ralf announced his start of work on "clockevent support". I
tried to write my own clockevent support by that time. I reuse some
part of Ralf's work but end up to remove more code. I left it in a
very unfinished state because I don't have a lot of time to spend on
it and it should be sufficient as a starting point.

There's only one timer_interrupt handler left. Since Linux generic
time code takes care, now, of SMP and others issues. I also removed the
hackish low level interrupt handler since I'm not sure it's a clear win.

All code dealing with hpt has been moved in a file called
"hpt.c". Therefore we can easily stop compiling this code on platforms
that don't have it or don't want to use it. For platforms that don't
use this timer, they need to add their own timer support, but with the
generic time code it now really easy, and code duplication should be
minimal.

I changed platform time init hooks since it seems to be the time for
that but I'm really not sure on that point though.

This patch is based on recent linux-mips repo
(linux-2.6.22-rc4-3-g38fc544) but I could rebase it on linux-time repo
if needed. If you want to compile/test it, you'll need to implement the
new platform hooks. It should be fairly simple for most of MIPS platforms...

Thanks,
--
              Franck
From 7b32a531ea1568411acc081da62f4ed66e42df97 Mon Sep 17 00:00:00 2001
From: Franck Bui-Huu <franck.bui-huu@xxxxxxxxxxxxxxx>
Date: Fri, 11 May 2007 09:55:35 +0200
Subject: [PATCH] clockevents support

Signed-off-by: Franck Bui-Huu <franck.bui-huu@xxxxxxxxxxxxxxx>
---
 arch/mips/Kconfig         |    9 ++
 arch/mips/kernel/Makefile |    4 +-
 arch/mips/kernel/hpt.c    |  221 +++++++++++++++++++++++++++++++++++++++++++++
 arch/mips/kernel/smp.c    |    1 +
 arch/mips/kernel/time2.c  |   84 +++++++++++++++++
 arch/mips/lib/Makefile    |    2 +-
 arch/mips/lib/time.c      |   52 +++++++++++
 arch/mips/usip/irq.c      |    5 +-
 include/asm-mips/time.h   |   23 ++----
 9 files changed, 377 insertions(+), 24 deletions(-)
 create mode 100644 arch/mips/kernel/hpt.c
 create mode 100644 arch/mips/kernel/time2.c
 create mode 100644 arch/mips/lib/time.c

diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index da253bc..3f7eaf3 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -723,6 +723,14 @@ config GENERIC_TIME
 	bool
 	default y
 
+config GENERIC_CLOCKEVENTS
+	bool
+	default y
+
+config GENERIC_CMOS_UPDATE
+	bool
+	default y
+
 config SCHED_NO_NO_OMIT_FRAME_POINTER
 	bool
 	default y
@@ -1737,6 +1745,7 @@ config HZ
 	default 1000 if HZ_1000
 	default 1024 if HZ_1024
 
+source "kernel/time/Kconfig"
 source "kernel/Kconfig.preempt"
 
 config MIPS_INSANE_LARGE
diff --git a/arch/mips/kernel/Makefile b/arch/mips/kernel/Makefile
index 4924626..f83670e 100644
--- a/arch/mips/kernel/Makefile
+++ b/arch/mips/kernel/Makefile
@@ -4,9 +4,9 @@
 
 extra-y		:= head.o init_task.o vmlinux.lds
 
-obj-y		+= cpu-probe.o branch.o entry.o genex.o irq.o process.o \
+obj-y		+= cpu-probe.o branch.o entry.o genex.o hpt.o irq.o process.o \
 		   ptrace.o reset.o semaphore.o setup.o signal.o syscall.o \
-		   time.o topology.o traps.o unaligned.o
+		   time2.o topology.o traps.o unaligned.o
 
 binfmt_irix-objs	:= irixelf.o irixinv.o irixioctl.o irixsig.o	\
 			   irix5sys.o sysirix.o
diff --git a/arch/mips/kernel/hpt.c b/arch/mips/kernel/hpt.c
new file mode 100644
index 0000000..31fe071
--- /dev/null
+++ b/arch/mips/kernel/hpt.c
@@ -0,0 +1,221 @@
+#include <linux/kernel_stat.h>
+#include <linux/spinlock.h>
+#include <linux/clockchips.h>
+#include <linux/clocksource.h>
+
+#include <asm/time.h>
+
+#define MIPS_HPT_NAME	"MIPS-HPT"
+
+unsigned int mips_hpt_frequency __read_mostly;
+
+/*
+ * cp0 hpt operations. Can be overriden by platform code
+ */
+void __weak mips_hpt_ack(void)
+{
+	write_c0_compare(read_c0_compare());
+}
+
+cycle_t __weak mips_hpt_read(void)
+{
+        return read_c0_count();
+}
+
+/*
+ * Clocksource
+ */
+struct clocksource hpt_clocksource = {
+	.name		= MIPS_HPT_NAME,
+	.mask		= CLOCKSOURCE_MASK(32),
+	.flags		= CLOCK_SOURCE_IS_CONTINUOUS,
+	.read		= mips_hpt_read,
+};
+
+static int mips_hpt_rating(void)
+{
+	return 200 + mips_hpt_frequency / 10000000;
+}
+
+static void __init setup_hpt_clocksource(void)
+{
+	u64 mult;
+	unsigned shift = 0;
+
+	for (shift = 32; shift > 0; shift--) {
+		mult = (u64)NSEC_PER_SEC << shift;
+		do_div(mult, mips_hpt_frequency);
+		if ((mult >> 32) == 0)
+			break;
+	}
+
+	hpt_clocksource.shift = shift;
+	hpt_clocksource.mult = mult;
+	hpt_clocksource.rating = mips_hpt_rating();
+
+	clocksource_register(&hpt_clocksource);
+}
+
+/*
+ * High precision timer functions for a R4k-compatible timer.
+ */
+
+static int mips_hpt_set_next_event(unsigned long delta,
+				   struct clock_event_device *evt)
+{
+	unsigned int cnt;
+
+	BUG_ON(evt->mode != CLOCK_EVT_MODE_ONESHOT);
+
+	/* interrupt ack is done by setting up the next event */
+	cnt = read_c0_count();
+	cnt += delta;
+	write_c0_compare(cnt);
+
+	return ((long)(read_c0_count() - cnt ) > 0) ? -ETIME : 0;
+}
+
+static void mips_hpt_setup(enum clock_event_mode mode,
+			   struct clock_event_device *evt)
+{
+	/* nothing to do */
+}
+
+static struct clock_event_device hpt_clockevent = {
+	.name		= MIPS_HPT_NAME,
+	.mode		= CLOCK_EVT_MODE_UNUSED,
+	.features	= CLOCK_EVT_FEAT_ONESHOT,
+	.shift		= 32,
+	.set_mode	= mips_hpt_setup,
+	.set_next_event	= mips_hpt_set_next_event,
+	.irq		= -1,
+};
+
+static DEFINE_PER_CPU(struct clock_event_device, mips_clock_events);
+
+static void __init finalize_hpt_clockevent(void)
+{
+	hpt_clockevent.mult = div_sc(mips_hpt_frequency, NSEC_PER_SEC, 32);
+	hpt_clockevent.max_delta_ns = clockevent_delta2ns(-1, &hpt_clockevent);
+	hpt_clockevent.min_delta_ns = clockevent_delta2ns(+1, &hpt_clockevent);
+	hpt_clockevent.rating = mips_hpt_rating();
+}
+
+void __init setup_hpt_clockevent(void)
+{
+	struct clock_event_device *cd;
+
+	cd = &__get_cpu_var(mips_clock_events);
+
+	memcpy(cd, &hpt_clockevent, sizeof(*cd));
+	cd->cpumask = cpumask_of_cpu(smp_processor_id());
+
+	clockevents_register_device(cd);
+}
+
+/*
+ * Performance counter IRQ or -1 if shared with timer
+ */
+int mipsxx_perfcount_irq;
+
+int null_perf_irq(void)
+{
+	return 0;
+}
+
+int (*perf_irq)(void) = null_perf_irq;
+
+EXPORT_SYMBOL(mipsxx_perfcount_irq);
+EXPORT_SYMBOL(null_perf_irq);
+EXPORT_SYMBOL(perf_irq);
+
+/*
+ * Possibly handle a performance counter interrupt.
+ * Return true if the timer interrupt should not be checked
+ */
+static inline int handle_perf_irq (int r2)
+{
+	/*
+	 * The performance counter overflow interrupt may be shared with the
+	 * timer interrupt (mipsxx_perfcount_irq < 0). If it is and a
+	 * performance counter has overflowed (perf_irq() == IRQ_HANDLED)
+	 * and we can't reliably determine if a counter interrupt has also
+	 * happened (!r2) then don't check for a timer interrupt.
+	 */
+	return mipsxx_perfcount_irq < 0 &&
+		perf_irq() == IRQ_HANDLED &&
+		!r2;
+}
+
+static irqreturn_t hpt_interrupt(int irq, void *dev_id)
+{
+	const int r2 = cpu_has_mips_r2;
+	struct clock_event_device *cd;
+
+	/*
+	 * Suckage alert:
+	 * Before R2 of the architecture there was no way to see if a
+	 * performance counter interrupt was pending, so we have to run
+	 * the performance counter interrupt handler anyway.
+	 */
+	if (handle_perf_irq(r2))
+		goto out;
+
+	/*
+	 * The same applies to performance counter interrupts.  But with the
+	 * above we now know that the reason we got here must be a timer
+	 * interrupt.  Being the paranoiacs we are we check anyway.
+	 */
+	if (!r2 || (read_c0_cause() & (1 << 30))) {
+		cd = &__get_cpu_var(mips_clock_events);
+
+		/*
+		 * We always ack the counter since we never shuts it down.
+		 * Therefore we can get interrupts whereas the hpt clock
+		 * event device has been disabled.
+		 */
+		mips_hpt_ack();
+
+		if (cd->mode != CLOCK_EVT_MODE_SHUTDOWN)
+			cd->event_handler(cd);
+	}
+
+out:
+	return IRQ_HANDLED;
+}
+
+
+struct irqaction hpt_irqaction = {
+	.handler	= hpt_interrupt,
+	.flags		= IRQF_DISABLED | IRQF_PERCPU,
+	.name		= "MIPS_HPT_NAME",
+};
+
+void __init hpt_timer_setup(struct irqaction *irq)
+{
+	if (!cpu_has_counter)
+		return;
+	if (mips_hpt_frequency == 0)
+		return;
+
+	finalize_hpt_clockevent();
+	setup_hpt_clockevent();
+
+	/* Enable hpt interrupt. */
+	plat_hpt_setup(&hpt_irqaction);
+}
+
+void __init hpt_clock_init(void)
+{
+	if (!cpu_has_counter)
+		return;
+	if (mips_hpt_frequency == 0)
+		/* FIXME: try to auto detect hpt frequency */
+		return;
+
+	printk("Using %u.%03u MHz high precision timer.\n",
+	       ((mips_hpt_frequency + 500) / 1000) / 1000,
+	       ((mips_hpt_frequency + 500) / 1000) % 1000);
+
+	setup_hpt_clocksource();
+}
diff --git a/arch/mips/kernel/smp.c b/arch/mips/kernel/smp.c
index 67edfa7..0d84d70 100644
--- a/arch/mips/kernel/smp.c
+++ b/arch/mips/kernel/smp.c
@@ -79,6 +79,7 @@ asmlinkage __cpuinit void start_secondary(void)
 	cpu_probe();
 	cpu_report();
 	per_cpu_trap_init();
+	setup_hpt_clockevent();
 	prom_init_secondary();
 
 	/*
diff --git a/arch/mips/kernel/time2.c b/arch/mips/kernel/time2.c
new file mode 100644
index 0000000..9e8500e
--- /dev/null
+++ b/arch/mips/kernel/time2.c
@@ -0,0 +1,84 @@
+#include <linux/kernel_stat.h>
+#include <linux/spinlock.h>
+#include <linux/clockchips.h>
+#include <linux/clocksource.h>
+
+#include <asm/time.h>
+
+/*
+ * RTC. By default we provide the null RTC hooks
+ */
+DEFINE_SPINLOCK(rtc_lock);
+EXPORT_SYMBOL(rtc_lock);
+
+unsigned long __weak mips_rtc_get_time(void)
+{
+	return mktime(2000, 1, 1, 0, 0, 0);
+}
+
+int __weak mips_rtc_set_time(unsigned long sec)
+{
+	return 0;
+}
+
+int __weak mips_rtc_set_mmss(unsigned long time)
+ {
+	return mips_rtc_set_time(time);
+ }
+
+int update_persistent_clock(struct timespec now)
+{
+	return mips_rtc_set_mmss(now.tv_sec);
+}
+
+#if 0
+/* FIXME: we need to init rtc earlier since timekeeping_init()
+ * is called before time_init().
+ */
+unsigned long read_persistent_clock(void)
+{
+	return mips_rtc_get_time();
+}
+#endif
+
+/* only during transition period */
+unsigned long rtc_mips_get_time(void)
+{
+	return mips_rtc_get_time();
+}
+EXPORT_SYMBOL(rtc_mips_get_time);
+
+int rtc_mips_set_time(unsigned long sec)
+{
+	return mips_rtc_set_time(sec);
+}
+EXPORT_SYMBOL(rtc_mips_set_time);
+
+/*
+ *
+ */
+void __init __weak plat_timer_setup(void)
+{
+}
+
+void __init time_init(void)
+{
+	/*
+	 * Mandatory platform hook. It basically setup the RTC.
+	 * FIXME: shouldn't we call these before calling
+	 * timekeeping_init() ?
+	 */
+	plat_time_init();
+	hpt_clock_init();
+
+	/*
+	 * For platforms which want to use the cpu counter as the
+	 * system timer
+	 */
+	hpt_timer_setup();
+
+	/*
+	 * Platform can setup a new tick device.
+	 */
+	plat_timer_setup();
+}
diff --git a/arch/mips/lib/Makefile b/arch/mips/lib/Makefile
index 5dad13e..447e803 100644
--- a/arch/mips/lib/Makefile
+++ b/arch/mips/lib/Makefile
@@ -3,7 +3,7 @@
 #
 
 lib-y	+= csum_partial.o memcpy.o memcpy-inatomic.o memset.o strlen_user.o \
-	   strncpy_user.o strnlen_user.o uncached.o
+	   strncpy_user.o strnlen_user.o time.o uncached.o
 
 obj-y			+= iomap.o
 obj-$(CONFIG_PCI)	+= iomap-pci.o
diff --git a/arch/mips/lib/time.c b/arch/mips/lib/time.c
new file mode 100644
index 0000000..e561050
--- /dev/null
+++ b/arch/mips/lib/time.c
@@ -0,0 +1,52 @@
+#include <asm/time.h>
+
+/*
+ * to_tm(). FIXME: should be shared with all archs...
+ */
+#define FEBRUARY		2
+#define STARTOFTIME		1970
+#define SECDAY			86400L
+#define SECYR			(SECDAY * 365)
+#define leapyear(y)		((!((y) % 4) && ((y) % 100)) || !((y) % 400))
+#define days_in_year(y)		(leapyear(y) ? 366 : 365)
+#define days_in_month(m)	(month_days[(m) - 1])
+
+static int month_days[12] = {
+	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+void to_tm(unsigned long tim, struct rtc_time *tm)
+{
+	long hms, day, gday;
+	int i;
+
+	gday = day = tim / SECDAY;
+	hms = tim % SECDAY;
+
+	/* Hours, minutes, seconds are easy */
+	tm->tm_hour = hms / 3600;
+	tm->tm_min = (hms % 3600) / 60;
+	tm->tm_sec = (hms % 3600) % 60;
+
+	/* Number of years in days */
+	for (i = STARTOFTIME; day >= days_in_year(i); i++)
+		day -= days_in_year(i);
+	tm->tm_year = i;
+
+	/* Number of months in days left */
+	if (leapyear(tm->tm_year))
+		days_in_month(FEBRUARY) = 29;
+	for (i = 1; day >= days_in_month(i); i++)
+		day -= days_in_month(i);
+	days_in_month(FEBRUARY) = 28;
+	tm->tm_mon = i - 1;		/* tm_mon starts from 0 to 11 */
+
+	/* Days are what is left over (+1) from all that. */
+	tm->tm_mday = day + 1;
+
+	/*
+	 * Determine the day of week
+	 */
+	tm->tm_wday = (gday + 4) % 7;	/* 1970/1/1 was Thursday */
+}
+EXPORT_SYMBOL(to_tm);
diff --git a/arch/mips/usip/irq.c b/arch/mips/usip/irq.c
index ba2bba4..13a04bc 100644
--- a/arch/mips/usip/irq.c
+++ b/arch/mips/usip/irq.c
@@ -160,10 +160,7 @@ asmlinkage void plat_irq_dispatch(void)
 		goto spurious;
 
 	irqnum = isr2irq_table[ip][__ffs(isr)];
-	if (irqnum == USIP_CPU_TIMER_IRQ)
-		ll_timer_interrupt(USIP_CPU_TIMER_IRQ);
-	else
-		do_IRQ(irqnum);
+	do_IRQ(irqnum);
 	return;
 spurious:
 	spurious_interrupt();
diff --git a/include/asm-mips/time.h b/include/asm-mips/time.h
index a632cef..b4e1e81 100644
--- a/include/asm-mips/time.h
+++ b/include/asm-mips/time.h
@@ -32,9 +32,9 @@ extern spinlock_t rtc_lock;
  *	rtc_mips_set_mmss - similar to rtc_set_time, but only min and sec need
  *			to be set.  Used by RTC sync-up.
  */
-extern unsigned long (*rtc_mips_get_time)(void);
-extern int (*rtc_mips_set_time)(unsigned long);
-extern int (*rtc_mips_set_mmss)(unsigned long);
+extern unsigned long rtc_mips_get_time(void);
+extern int rtc_mips_set_time(unsigned long);
+extern int rtc_mips_set_mmss(unsigned long);
 
 /*
  * Timer interrupt functions.
@@ -48,7 +48,7 @@ extern void (*mips_timer_ack)(void);
  * High precision timer clocksource.
  * If .read is NULL, an R4k-compatible timer setup is attempted.
  */
-extern struct clocksource clocksource_mips;
+extern struct clocksource mips_clocksource;
 
 /*
  * to_tm() converts system time back to (year, mon, day, hour, min, sec).
@@ -58,20 +58,9 @@ extern struct clocksource clocksource_mips;
 extern void to_tm(unsigned long tim, struct rtc_time *tm);
 
 /*
- * high-level timer interrupt routines.
- */
-extern irqreturn_t timer_interrupt(int irq, void *dev_id);
-
-/*
  * the corresponding low-level timer interrupt routine.
  */
-extern asmlinkage void ll_timer_interrupt(int irq);
-
-/*
- * profiling and process accouting is done separately in local_timer_interrupt
- */
-extern void local_timer_interrupt(int irq, void *dev_id);
-extern asmlinkage void ll_local_timer_interrupt(int irq);
+extern asmlinkage irqreturn_t ll_timer_interrupt(int irq, void *dev_id);
 
 /*
  * board specific routines required by time_init().
@@ -80,7 +69,7 @@ extern asmlinkage void ll_local_timer_interrupt(int irq);
  */
 struct irqaction;
 extern void (*board_time_init)(void);
-extern void plat_timer_setup(struct irqaction *irq);
+extern void plat_timer_setup(void);
 
 /*
  * mips_hpt_frequency - must be set if you intend to use an R4k-compatible
-- 
1.5.2.1


[Index of Archives]     [Linux MIPS Home]     [LKML Archive]     [Linux ARM Kernel]     [Linux ARM]     [Linux]     [Git]     [Yosemite News]     [Linux SCSI]     [Linux Hams]

  Powered by Linux