[MIPS] Dyntick support for SMTC:

The kernel currently only supports broadcasting of the timer interrupt
from a single timer, not multicasting into two multicast groups of
processors.  So the implemented mechanism for SMTC works by broadcasting
the cp0 compare interrupt on VPE 0 and ignoring it on any additional VPEs.

Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index ecce3aa..d8c9058 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -1368,6 +1368,7 @@
 	depends on CPU_MIPS32_R2
 	#depends on CPU_MIPS64_R2		# once there is hardware ...
 	depends on SYS_SUPPORTS_MULTITHREADING
+	select GENERIC_CLOCKEVENTS_BROADCAST
 	select CPU_MIPSR2_IRQ_VI
 	select CPU_MIPSR2_IRQ_EI
 	select CPU_MIPSR2_SRS
@@ -1537,6 +1538,9 @@
 	depends on !CPU_R3000
 	default y
 
+config GENERIC_CLOCKEVENTS_BROADCAST
+	bool
+
 #
 # Use the generic interrupt handling code in kernel/irq/:
 #
diff --git a/arch/mips/kernel/smtc.c b/arch/mips/kernel/smtc.c
index 137183b..4d1ac96 100644
--- a/arch/mips/kernel/smtc.c
+++ b/arch/mips/kernel/smtc.c
@@ -1,5 +1,6 @@
 /* Copyright (C) 2004 Mips Technologies, Inc */
 
+#include <linux/clockchips.h>
 #include <linux/kernel.h>
 #include <linux/sched.h>
 #include <linux/cpumask.h>
@@ -62,7 +63,7 @@
  * Clock interrupt "latch" buffers, per "CPU"
  */
 
-unsigned int ipi_timer_latch[NR_CPUS];
+static atomic_t ipi_timer_latch[NR_CPUS];
 
 /*
  * Number of InterProcessor Interupt (IPI) message buffers to allocate
@@ -296,8 +297,10 @@
 		__cpu_number_map[i] = i;
 		__cpu_logical_map[i] = i;
 	}
+#ifdef CONFIG_MIPS_MT_FPAFF
 	/* Initialize map of CPUs with FPUs */
 	cpus_clear(mt_fpu_cpumask);
+#endif
 
 	/* One of those TC's is the one booting, and not a secondary... */
 	printk("%i available secondary CPU TC(s)\n", i - 1);
@@ -359,7 +362,7 @@
 		IPIQ[i].head = IPIQ[i].tail = NULL;
 		spin_lock_init(&IPIQ[i].lock);
 		IPIQ[i].depth = 0;
-		ipi_timer_latch[i] = 0;
+		atomic_set(&ipi_timer_latch[i], 0);
 	}
 
 	/* cpu_data index starts at zero */
@@ -482,10 +485,12 @@
 
 	/* Set up coprocessor affinity CPU mask(s) */
 
+#ifdef CONFIG_MIPS_MT_FPAFF
 	for (tc = 0; tc < ntc; tc++) {
 		if (cpu_data[tc].options & MIPS_CPU_FPU)
 			cpu_set(tc, mt_fpu_cpumask);
 	}
+#endif
 
 	/* set up ipi interrupts... */
 
@@ -702,7 +707,7 @@
  * be done with the atomic.h primitives). And since this is
  * MIPS MT, we can assume that we have LL/SC.
  */
-static __inline__ int atomic_postincrement(unsigned int *pv)
+static inline int atomic_postincrement(atomic_t *v)
 {
 	unsigned long result;
 
@@ -714,8 +719,8 @@
 	"	sc	%1, %2					\n"
 	"	beqz	%1, 1b					\n"
 	__WEAK_LLSC_MB
-	: "=&r" (result), "=&r" (temp), "=m" (*pv)
-	: "m" (*pv)
+	: "=&r" (result), "=&r" (temp), "=m" (v->counter)
+	: "m" (v->counter)
 	: "memory");
 
 	return result;
@@ -743,6 +748,8 @@
 	pipi->arg = (void *)action;
 	pipi->dest = cpu;
 	if (cpu_data[cpu].vpe_id != cpu_data[smp_processor_id()].vpe_id) {
+		if (type == SMTC_CLOCK_TICK)
+			atomic_inc(&ipi_timer_latch[cpu]);
 		/* If not on same VPE, enqueue and send cross-VPE interupt */
 		smtc_ipi_nq(&IPIQ[cpu], pipi);
 		LOCK_CORE_PRA();
@@ -784,6 +791,8 @@
 			}
 			smtc_ipi_nq(&IPIQ[cpu], pipi);
 		} else {
+			if (type == SMTC_CLOCK_TICK)
+				atomic_inc(&ipi_timer_latch[cpu]);
 			post_direct_ipi(cpu, pipi);
 			write_tc_c0_tchalt(0);
 			UNLOCK_CORE_PRA();
@@ -801,6 +810,7 @@
 	unsigned long tcrestart;
 	extern u32 kernelsp[NR_CPUS];
 	extern void __smtc_ipi_vector(void);
+//printk("%s: on %d for %d\n", __func__, smp_processor_id(), cpu);
 
 	/* Extract Status, EPC from halted TC */
 	tcstatus = read_tc_c0_tcstatus();
@@ -851,25 +861,31 @@
 	smp_call_function_interrupt();
 }
 
+DECLARE_PER_CPU(struct clock_event_device, smtc_dummy_clockevent_device);
+
 void ipi_decode(struct smtc_ipi *pipi)
 {
+	unsigned int cpu = smp_processor_id();
+	struct clock_event_device *cd;
 	void *arg_copy = pipi->arg;
 	int type_copy = pipi->type;
-	int dest_copy = pipi->dest;
+	int ticks;
 
 	smtc_ipi_nq(&freeIPIq, pipi);
 	switch (type_copy) {
 	case SMTC_CLOCK_TICK:
 		irq_enter();
-		kstat_this_cpu.irqs[MIPS_CPU_IRQ_BASE + cp0_compare_irq]++;
-		/* Invoke Clock "Interrupt" */
-		ipi_timer_latch[dest_copy] = 0;
-#ifdef CONFIG_SMTC_IDLE_HOOK_DEBUG
-		clock_hang_reported[dest_copy] = 0;
-#endif /* CONFIG_SMTC_IDLE_HOOK_DEBUG */
-		local_timer_interrupt(0, NULL);
+		kstat_this_cpu.irqs[MIPS_CPU_IRQ_BASE + 1]++;
+		cd = &per_cpu(smtc_dummy_clockevent_device, cpu);
+		ticks = atomic_read(&ipi_timer_latch[cpu]);
+		atomic_sub(ticks, &ipi_timer_latch[cpu]);
+		while (ticks) {
+			cd->event_handler(cd);
+			ticks--;
+		}
 		irq_exit();
 		break;
+
 	case LINUX_SMP_IPI:
 		switch ((int)arg_copy) {
 		case SMP_RESCHEDULE_YOURSELF:
@@ -921,25 +937,6 @@
 }
 
 /*
- * Send clock tick to all TCs except the one executing the funtion
- */
-
-void smtc_timer_broadcast(void)
-{
-	int cpu;
-	int myTC = cpu_data[smp_processor_id()].tc_id;
-	int myVPE = cpu_data[smp_processor_id()].vpe_id;
-
-	smtc_cpu_stats[smp_processor_id()].timerints++;
-
-	for_each_online_cpu(cpu) {
-		if (cpu_data[cpu].vpe_id == myVPE &&
-		    cpu_data[cpu].tc_id != myTC)
-			smtc_send_ipi(cpu, SMTC_CLOCK_TICK, 0);
-	}
-}
-
-/*
  * Cross-VPE interrupts in the SMTC prototype use "software interrupts"
  * set via cross-VPE MTTR manipulation of the Cause register. It would be
  * in some regards preferable to have external logic for "doorbell" hardware
@@ -1180,11 +1177,11 @@
 	for (tc = 0; tc < NR_CPUS; tc++) {
 		/* Don't check ourself - we'll dequeue IPIs just below */
 		if ((tc != smp_processor_id()) &&
-		    ipi_timer_latch[tc] > timerq_limit) {
+		    atomic_read(&ipi_timer_latch[tc]) > timerq_limit) {
 		    if (clock_hang_reported[tc] == 0) {
 			pdb_msg += sprintf(pdb_msg,
 				"TC %d looks hung with timer latch at %d\n",
-				tc, ipi_timer_latch[tc]);
+				tc, atomic_read(&ipi_timer_latch[tc]));
 			clock_hang_reported[tc]++;
 			}
 		}
@@ -1225,7 +1222,7 @@
 	smtc_ipi_qdump();
 	printk("Timer IPI Backlogs:\n");
 	for (i=0; i < NR_CPUS; i++) {
-		printk("%d: %d\n", i, ipi_timer_latch[i]);
+		printk("%d: %d\n", i, atomic_read(&ipi_timer_latch[i]));
 	}
 	printk("%d Recoveries of \"stolen\" FPU\n",
 	       atomic_read(&smtc_fpu_recoveries));
diff --git a/arch/mips/kernel/time.c b/arch/mips/kernel/time.c
index 3598884..369a5f9 100644
--- a/arch/mips/kernel/time.c
+++ b/arch/mips/kernel/time.c
@@ -25,6 +25,7 @@
 #include <linux/spinlock.h>
 #include <linux/interrupt.h>
 #include <linux/module.h>
+#include <linux/kallsyms.h>
 
 #include <asm/bootinfo.h>
 #include <asm/cache.h>
@@ -33,6 +34,7 @@
 #include <asm/cpu-features.h>
 #include <asm/div64.h>
 #include <asm/sections.h>
+#include <asm/smtc_ipi.h>
 #include <asm/time.h>
 
 #include <irq.h>
@@ -230,12 +232,24 @@
                            struct clock_event_device *evt)
 {
 	unsigned int cnt;
+	int res;
 
+#ifdef CONFIG_MIPS_MT_SMTC
+	{
+	unsigned long flags, vpflags;
+	local_irq_save(flags);
+	vpflags = dvpe();
+#endif
 	cnt = read_c0_count();
 	cnt += delta;
 	write_c0_compare(cnt);
-
-	return ((long)(read_c0_count() - cnt ) > 0) ? -ETIME : 0;
+	res = ((long)(read_c0_count() - cnt ) > 0) ? -ETIME : 0;
+#ifdef CONFIG_MIPS_MT_SMTC
+	evpe(vpflags);
+	local_irq_restore(flags);
+	}
+#endif
+	return res;
 }
 
 static void mips_set_mode(enum clock_event_mode mode,
@@ -244,9 +258,7 @@
 	/* Nothing to do ...  */
 }
 
-struct clock_event_device mips_clockevent;
-
-static struct clock_event_device *global_cd[NR_CPUS];
+static DEFINE_PER_CPU(struct clock_event_device, mips_clockevent_device);
 static int cp0_timer_irq_installed;
 
 static irqreturn_t timer_interrupt(int irq, void *dev_id)
@@ -271,7 +283,12 @@
 	 */
 	if (!r2 || (read_c0_cause() & (1 << 30))) {
 		c0_timer_ack();
-		cd = global_cd[cpu];
+#ifdef CONFIG_MIPS_MT_SMTC
+		if (cpu_data[cpu].vpe_id)
+			goto out;
+		cpu = 0;
+#endif
+		cd = &per_cpu(mips_clockevent_device, cpu);
 		cd->event_handler(cd);
 	}
 
@@ -281,7 +298,11 @@
 
 static struct irqaction timer_irqaction = {
 	.handler = timer_interrupt,
+#ifdef CONFIG_MIPS_MT_SMTC
+	.flags = IRQF_DISABLED,
+#else
 	.flags = IRQF_DISABLED | IRQF_PERCPU,
+#endif
 	.name = "timer",
 };
 
@@ -316,6 +337,60 @@
 {
 }
 
+#ifdef CONFIG_MIPS_MT_SMTC
+DEFINE_PER_CPU(struct clock_event_device, smtc_dummy_clockevent_device);
+
+static void smtc_set_mode(enum clock_event_mode mode,
+                          struct clock_event_device *evt)
+{
+}
+
+int dummycnt[NR_CPUS];
+
+static void mips_broadcast(cpumask_t mask)
+{
+	unsigned int cpu;
+
+	for_each_cpu_mask(cpu, mask)
+		smtc_send_ipi(cpu, SMTC_CLOCK_TICK, 0);
+}
+
+static void setup_smtc_dummy_clockevent_device(void)
+{
+	//uint64_t mips_freq = mips_hpt_^frequency;
+	unsigned int cpu = smp_processor_id();
+	struct clock_event_device *cd;
+
+	cd = &per_cpu(smtc_dummy_clockevent_device, cpu);
+
+	cd->name		= "SMTC";
+	cd->features		= CLOCK_EVT_FEAT_DUMMY;
+
+	/* Calculate the min / max delta */
+	cd->mult	= 0; //div_sc((unsigned long) mips_freq, NSEC_PER_SEC, 32);
+	cd->shift		= 0; //32;
+	cd->max_delta_ns	= 0; //clockevent_delta2ns(0x7fffffff, cd);
+	cd->min_delta_ns	= 0; //clockevent_delta2ns(0x30, cd);
+
+	cd->rating		= 200;
+	cd->irq			= 17; //-1;
+//	if (cpu)
+//		cd->cpumask	= CPU_MASK_ALL; // cpumask_of_cpu(cpu);
+//	else
+		cd->cpumask	= cpumask_of_cpu(cpu);
+
+	cd->set_mode		= smtc_set_mode;
+
+	cd->broadcast		= mips_broadcast;
+
+	clockevents_register_device(cd);
+}
+#endif
+
+static void mips_event_handler(struct clock_event_device *dev)
+{
+}
+
 void __cpuinit mips_clockevent_init(void)
 {
 	uint64_t mips_freq = mips_hpt_frequency;
@@ -326,12 +401,18 @@
 	if (!cpu_has_counter)
 		return;
 
-	if (cpu == 0)
-		cd = &mips_clockevent;
-	else
-		cd = kzalloc(sizeof(*cd), GFP_ATOMIC);
-	if (!cd)
-		return;		/* We're probably roadkill ...  */
+#ifdef CONFIG_MIPS_MT_SMTC
+	setup_smtc_dummy_clockevent_device();
+
+	/*
+	 * On SMTC we only register VPE0's compare interrupt as clockevent
+	 * device.
+	 */
+	if (cpu)
+		return;
+#endif
+
+	cd = &per_cpu(mips_clockevent_device, cpu);
 
 	cd->name		= "MIPS";
 	cd->features		= CLOCK_EVT_FEAT_ONESHOT;
@@ -344,11 +425,15 @@
 
 	cd->rating		= 300;
 	cd->irq			= irq;
+#ifdef CONFIG_MIPS_MT_SMTC
+	cd->cpumask		= CPU_MASK_ALL;
+#else
 	cd->cpumask		= cpumask_of_cpu(cpu);
+#endif
 	cd->set_next_event	= mips_next_event;
 	cd->set_mode		= mips_set_mode;
+	cd->event_handler	= mips_event_handler;
 
-	global_cd[cpu] = cd;
 	clockevents_register_device(cd);
 
 	if (!cp0_timer_irq_installed) {
diff --git a/arch/mips/mips-boards/generic/time.c b/arch/mips/mips-boards/generic/time.c
index 2c8db3e..cf55ecd 100644
--- a/arch/mips/mips-boards/generic/time.c
+++ b/arch/mips/mips-boards/generic/time.c
@@ -55,7 +55,6 @@
 
 static int mips_cpu_timer_irq;
 extern int cp0_perfcount_irq;
-extern void smtc_timer_broadcast(void);
 
 static void mips_timer_dispatch(void)
 {