Commit ce662dbb authored by Benjamin Herrenschmidt's avatar Benjamin Herrenschmidt Committed by Linus Torvalds

[PATCH] ppc64: Add HW CPU timebase sync

This 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 replaces
the software sync algorithm we used so far and provides slightly more
precise results.
Signed-off-by: default avatarBenjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent e0e04bff
......@@ -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 *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 @@ static void __init smp_core99_kick_cpu(int nr)
* 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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment