
From: Benjamin Herrenschmidt <benh@kernel.crashing.org>

This patch which requires "ppc64: Fix G5 low level i2c code" to be applied
first, implements support for doing a HW synchronization of the CPU
timebases on SMP G5 machines.  When the proper clock chips are found on
i2c, they are used to stop the timebase clock source during the
synchronization process.  This replace the software sync algorithm we used
so far and provide slightly more precise results.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/arch/ppc64/kernel/pmac_smp.c |  154 ++++++++++++++++++++++++++++++++---
 1 files changed, 144 insertions(+), 10 deletions(-)

diff -puN arch/ppc64/kernel/pmac_smp.c~ppc64-add-hw-cpu-timebase-sync arch/ppc64/kernel/pmac_smp.c
--- 25/arch/ppc64/kernel/pmac_smp.c~ppc64-add-hw-cpu-timebase-sync	2004-11-09 00:10:18.221678136 -0800
+++ 25-akpm/arch/ppc64/kernel/pmac_smp.c	2004-11-09 00:10:18.225677528 -0800
@@ -31,7 +31,6 @@
 #include <linux/smp_lock.h>
 #include <linux/interrupt.h>
 #include <linux/kernel_stat.h>
-#include <linux/delay.h>
 #include <linux/init.h>
 #include <linux/spinlock.h>
 #include <linux/errno.h>
@@ -51,6 +50,7 @@
 #include <asm/time.h>
 #include <asm/cacheflush.h>
 #include <asm/keylargo.h>
+#include <asm/pmac_low_i2c.h>
 
 #include "mpic.h"
 
@@ -66,31 +66,164 @@ extern void pmac_secondary_start_3(void)
 
 extern struct smp_ops_t *smp_ops;
 
+static void (*pmac_tb_freeze)(int freeze);
+static struct device_node *pmac_tb_clock_chip_host;
+static spinlock_t timebase_lock = SPIN_LOCK_UNLOCKED;
+static unsigned long timebase;
+
+static void smp_core99_cypress_tb_freeze(int freeze)
+{
+	u8 data;
+	int rc;
+
+	/* Strangely, the device-tree says address is 0xd2, but darwin
+	 * accesses 0xd0 ...
+	 */
+	pmac_low_i2c_setmode(pmac_tb_clock_chip_host, pmac_low_i2c_mode_combined);
+	rc = pmac_low_i2c_xfer(pmac_tb_clock_chip_host,
+			       0xd0 | pmac_low_i2c_read,
+			       0x81, &data, 1);
+	if (rc != 0)
+		goto bail;
+
+	data = (data & 0xf3) | (freeze ? 0x00 : 0x0c);
+
+       	pmac_low_i2c_setmode(pmac_tb_clock_chip_host, pmac_low_i2c_mode_stdsub);
+	rc = pmac_low_i2c_xfer(pmac_tb_clock_chip_host,
+			       0xd0 | pmac_low_i2c_write,
+			       0x81, &data, 1);
+
+ bail:
+	if (rc != 0) {
+		printk("Cypress Timebase %s rc: %d\n",
+		       freeze ? "freeze" : "unfreeze", rc);
+		panic("Timebase freeze failed !\n");
+	}
+}
+
+static void smp_core99_pulsar_tb_freeze(int freeze)
+{
+	u8 data;
+	int rc;
+
+	/* Strangely, the device-tree says address is 0xd2, but darwin
+	 * accesses 0xd0 ...
+	 */
+	pmac_low_i2c_setmode(pmac_tb_clock_chip_host, pmac_low_i2c_mode_combined);
+	rc = pmac_low_i2c_xfer(pmac_tb_clock_chip_host,
+			       0xd4 | pmac_low_i2c_read,
+			       0x2e, &data, 1);
+	if (rc != 0)
+		goto bail;
+
+	data = (data & 0x88) | (freeze ? 0x11 : 0x22);
+
+	pmac_low_i2c_setmode(pmac_tb_clock_chip_host, pmac_low_i2c_mode_stdsub);
+	rc = pmac_low_i2c_xfer(pmac_tb_clock_chip_host,
+			       0xd4 | pmac_low_i2c_write,
+			       0x2e, &data, 1);
+ bail:
+	if (rc != 0) {
+		printk(KERN_ERR "Pulsar Timebase %s rc: %d\n",
+		       freeze ? "freeze" : "unfreeze", rc);
+		panic("Timebase freeze failed !\n");
+	}
+}
+
+
+static void smp_core99_give_timebase(void)
+{
+	/* Open i2c bus for synchronous access */
+	if (pmac_low_i2c_open(pmac_tb_clock_chip_host, 0))
+		panic("Can't open i2c for TB sync !\n");
+
+	spin_lock(&timebase_lock);
+	(*pmac_tb_freeze)(1);
+	mb();
+	timebase = get_tb();
+	spin_unlock(&timebase_lock);
+
+	while (timebase)
+		barrier();
+
+	spin_lock(&timebase_lock);
+	(*pmac_tb_freeze)(0);
+	spin_unlock(&timebase_lock);
+
+	/* Close i2c bus */
+	pmac_low_i2c_close(pmac_tb_clock_chip_host);
+}
+
+
+static void __devinit smp_core99_take_timebase(void)
+{
+	while (!timebase)
+		barrier();
+	spin_lock(&timebase_lock);
+	set_tb(timebase >> 32, timebase & 0xffffffff);
+	timebase = 0;
+	spin_unlock(&timebase_lock);
+}
+
+
 static int __init smp_core99_probe(void)
 {
 	struct device_node *cpus;
-	int ncpus = 1;
+	struct device_node *cc;
+	int ncpus = 0;
 
 	/* Maybe use systemconfiguration here ? */
 	if (ppc_md.progress) ppc_md.progress("smp_core99_probe", 0x345);
-	cpus = find_type_devices("cpu");
-	if (cpus == NULL)
-		return 0;
 
-       	while ((cpus = cpus->next) != NULL)
+	/* Count CPUs in the device-tree */
+       	for (cpus = NULL; (cpus = of_find_node_by_type(cpus, "cpu")) != NULL;)
 	       	++ncpus;
 
 	printk(KERN_INFO "PowerMac SMP probe found %d cpus\n", ncpus);
 
-	if (ncpus > 1)
-		mpic_request_ipis();
+	/* Nothing more to do if less than 2 of them */
+	if (ncpus <= 1)
+		return 1;
+
+	/* Look for the clock chip */
+	for (cc = NULL; (cc = of_find_node_by_name(cc, "i2c-hwclock")) != NULL;) {
+		struct device_node *p = of_get_parent(cc);
+		u32 *reg;
+		int ok;
+		ok = p && device_is_compatible(p, "uni-n-i2c");
+		if (!ok)
+			goto next;
+		reg = (u32 *)get_property(cc, "reg", NULL);
+		if (reg == NULL)
+			goto next;
+		switch (*reg) {
+		case 0xd2:
+			pmac_tb_freeze = smp_core99_cypress_tb_freeze;
+			printk(KERN_INFO "Timebase clock is Cypress chip\n");
+			break;
+		case 0xd4:
+			pmac_tb_freeze = smp_core99_pulsar_tb_freeze;
+			printk(KERN_INFO "Timebase clock is Pulsar chip\n");
+			break;
+		}
+		if (pmac_tb_freeze != NULL) {
+			pmac_tb_clock_chip_host = p;
+			smp_ops->give_timebase = smp_core99_give_timebase;
+			smp_ops->take_timebase = smp_core99_take_timebase;
+			break;
+		}
+	next:
+		of_node_put(p);
+	}
+
+	mpic_request_ipis();
 
 	return ncpus;
 }
 
 static void __init smp_core99_kick_cpu(int nr)
 {
-	int save_vector;
+	int save_vector, j;
 	unsigned long new_vector;
 	unsigned long flags;
 	volatile unsigned int *vector
@@ -135,7 +268,8 @@ static void __init smp_core99_kick_cpu(i
 	 * ideally, all that crap will be done in prom.c and the CPU left
 	 * in a RAM-based wait loop like CHRP.
 	 */
-	mdelay(1);
+	for (j = 1; j < 1000000; j++)
+		mb();
 
 	/* Restore our exception vector */
 	*vector = save_vector;
_
