[PATCH] ppc64: Add HW CPU timebase sync

Benjamin Herrenschmidt benh at kernel.crashing.org
Tue Nov 9 19:01:49 EST 2004


Hi !

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 at kernel.crashing.org>

Index: linux-work/arch/ppc64/kernel/pmac_smp.c
===================================================================
--- linux-work.orig/arch/ppc64/kernel/pmac_smp.c	2004-10-25 21:58:12.000000000 +1000
+++ linux-work/arch/ppc64/kernel/pmac_smp.c	2004-11-09 18:54:46.658302632 +1100
@@ -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 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 *cpus;	
+	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 @@
 	 * 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;





More information about the Linuxppc64-dev mailing list