Subject: 12.5 MHz woo hoo!

From: Kevin Diggs <[email-masked]>
Date: Fri, 15 Aug 2008 14:36:42 -0700

[root@PowerMac8600B root]# cat /proc/cpuinfo
processor       : 0
cpu             : 750GX
temperature     : 1-76 C (uncalibrated)
clock           : 200.000000MHz
revision        : 2.3 (pvr 0008 0203)
bogomips        : 24.96
timebase        : 12500000	<-- 12.5 MHz exactly!!!
platform        : PowerMac
model           : Power Macintosh
machine         : Power Macintosh
motherboard     : AAPL,8500 MacRISC
detected as     : 16 (PowerMac 8500/8600)
pmac flags      : 00000000
pmac-generation : OldWorld

Hey, anybody know if that temperature, thermal thingy works well enough 
to bother fooling with the code to produce a more valid value?

also:

[root@PowerMac8600B root]# alias tis
alias tis='cat /sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state'
[root@PowerMac8600B root]# tis
250000 178419
300000 0
350000 0
400000 0
450000 0
500000 0
550000 0
600000 0
650000 0
700000 0
750000 0
800000 0
850000 0
900000 0
950000 0
1000000 0

[root@PowerMac8600B root]# uname -vr
2.6.26-pll #4 Thu Aug 14 04:02:58 PDT 2008

Finally, can someone tell me if the attached file shows up ok if it were 
a patch I wanted to submit? I can't seem to figure out how to 'import 
inline' using this ancient mailer.

kevin
/*
 * cf750gx.c - cpufreq driver for the dual PLLs in the 750gx
 * ($Revision: 1.0 $)
 *
 *  Copyright (C) 2008       kevin Diggs
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or (at
 *  your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */
#define DEBUG

#include "linux/init.h"
#include "linux/module.h"
#include <linux/autoconf.h>
#include "linux/kernel.h"
#include <linux/errno.h>
#include <linux/cpu.h>
#include "linux/of.h"
#include "linux/notifier.h"
#include "linux/delay.h"

#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS
#include "linux/sysdev.h"
#endif
#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_HRTIMER
#include "linux/hrtimer.h"
#endif

#include <asm/uaccess.h>
#include <asm/bitops.h>
#include "asm/time.h"
#include <asm/mmu.h>
#include <asm/processor.h>
#include <asm/io.h>
#include <asm/pgtable.h>
#include <asm/cputable.h>
#include <asm/system.h>
#include <asm/pll_if.h>
#include <asm/pll.h>
#include <asm/smp.h>

MODULE_LICENSE("GPL");

static unsigned int boot_ratio;
static unsigned int pllifvBusClock;

static unsigned int override_bus_clock = 0;

#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_HRTIMER
static enum hrtimer_restart pllTimerF(struct hrtimer *hrt);
static struct hrtimer pll_timer;
static unsigned long hrtimers_got_no_freakin_callback_data;
#ifdef DEBUG
cycles_t pll_time_stamp;
#endif
#else
static void pllTimerF(unsigned long newPLL);
static struct timer_list pll_timer;
cycles_t pll_time_stamp;
#endif

#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS
extern unsigned long loops_per_jiffy;

unsigned long boot_loops;
static struct sys_device *sysdev_cpu;

static ssize_t show_ppc750gxpll(struct sys_device *dev, char *buf)
{
	return sprintf(buf, "%x\n", get_PLL());
}

int modifyPLL(unsigned int pll, int scaleLPJ);

//static ssize_t __attribute_used__ store_ppc750gxpll(struct sys_device *dev,
//	const char *buf, size_t count)
static ssize_t __used store_ppc750gxpll(struct sys_device *dev,
	const char *buf, size_t count)
{
unsigned int pll;
char *ptr;

	pr_debug(__FILE__">%s()-%d:  buf=%s\n", __func__, __LINE__, buf);

	pll = simple_strtoul(buf, &ptr, 16);

	pr_debug(__FILE__">%s()-%d:  %x (%d)\n", __func__, __LINE__, pll, pll);

/*	modifyPLL(pll,!0); */
	modifyPLL(pll, 0);

	return ptr-buf;
}

static SYSDEV_ATTR(ppc750gxpll, 0600, show_ppc750gxpll, store_ppc750gxpll);
#endif

#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_CPU_FREQ
struct plliftCallData {
	void *data;
	int scalar;
};

static struct plliftCallData pllifvSwitchCallData;
static struct plliftCallData pllifvLockCallData;
static RAW_NOTIFIER_HEAD(pllifvPllSwitchChain);
static RAW_NOTIFIER_HEAD(pllifvPllLockChain);
#endif

/*
 * This initializes the code for the PLL control:
 * boot_ratio is used to scale the loops_per_jiffy value from its boot value
 * boot_loops is the boot value of loops_per_jiffy and is used to compute new
 * values
 */
static int __init init_PLL(void)
{
unsigned int temp;
#ifdef CONFIG_PPC_OF
const u32 *clk;
struct device_node *tree_root;
#endif

	if (!cpu_has_feature(CPU_FTR_DUAL_PLL_750FX))
		return -ENODEV;

	boot_ratio = 0;

	/*
	 * See if bus clock override was specified
	 */
	if (override_bus_clock)
		pllifvBusClock = override_bus_clock*1000;

#ifdef CONFIG_PPC_OF
	/*
	 * If bus clock is not specified, try to get it via OF
	 */
	if (!pllifvBusClock) {
		/*
		 * Get root node (aka MacRISC bus)
		 */
		tree_root = of_find_node_by_name(NULL, "");

		if (tree_root) {
			clk = of_get_property(tree_root, "clock-frequency",
				NULL);

			if (clk && *clk)
				pllifvBusClock = (unsigned int) *clk;

			of_node_put(tree_root);

			pr_debug(__FILE__">%s()-%d:  Bus clock from OF is %u\n",
				__func__, __LINE__, pllifvBusClock);
		}
	}
#endif /* CONFIG_PPC_OF */
#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS
	temp = get_PLL();
	temp = get_PLL_ratio(get_active_PLL(temp), temp);

	/*
	 * Units for boot ratio is halves, i.e. 20 is a ratio of 10.
	 * From 21 on the returned value needs to be converted to halves.
	 */
	if (temp > 20)
		temp = (temp-10)<<1;

	boot_ratio = temp;
	boot_loops = loops_per_jiffy;

	/*
	 * Try to get the cpu sysdev
	 */
	sysdev_cpu = get_cpu_sysdev(boot_cpuid);

	if (sysdev_cpu != NULL)
		sysdev_create_file(sysdev_cpu, &attr_ppc750gxpll);
#endif

#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_HRTIMER
	hrtimer_init(&pll_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
#else
	init_timer(&pll_timer);
#endif

	pll_timer.function = pllTimerF;

	return 0;
}

/*__initcall(init_PLL); */

static void exit_PLL(void)
{
#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS
	if (sysdev_cpu != NULL)
		sysdev_remove_file(sysdev_cpu, &attr_ppc750gxpll);
#endif

	/*
	 * Make sure there are no timers pending by making sure we are not
	 * doing anything
	 */
	while (test_bit(PLL_LOCK_BIT, (unsigned long *)&boot_ratio))
		msleep(1);
}

module_init(init_PLL);
module_exit(exit_PLL);

#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS
static unsigned long pllNewLPJ(unsigned int oldRatio, unsigned int newRatio,
	unsigned long LPJ)
{
	if (LPJ > 200000000)
		return LPJ/oldRatio*newRatio;
	else
		return LPJ*newRatio/oldRatio;
}

static inline void pllUpdateLPJ(unsigned int oldRatio, unsigned int newRatio,
	unsigned long LPJ)
{
	loops_per_jiffy = pllNewLPJ(oldRatio, newRatio, LPJ);
}
#else
#define pllUpdateLPJ(a, b, c)
#endif

static void pllifiSwitchPLLs(unsigned int newPLL)
{
#if 0
unsigned long flags;
#endif
unsigned int new_ratio, new_ratio_cp, old_ratio, current_pll, masked_boot_ratio;

pr_debug(__FILE__">%s()-%d:  newPLL=0x%08x\n", __func__, __LINE__, newPLL);

	/*
	 * Compute new loops_per_jiffy
	 */
	current_pll = get_PLL();
	new_ratio = get_PLL_ratio(get_next_PLL(newPLL), current_pll);
	old_ratio = get_PLL_ratio(get_active_PLL(current_pll), current_pll);
	masked_boot_ratio = boot_ratio&0xff;
	new_ratio_cp = new_ratio;

	pr_debug(__FILE__">%s()-%d:  current_pll=0x%08x, new=%d, old=%d\n",
		__func__, __LINE__, current_pll, new_ratio, old_ratio);

	current_pll = (current_pll&~PLL_SEL_MASK)|(newPLL&PLL_SEL_MASK);

	pr_debug(__FILE__">%s()-%d:  current_pll=0x%08x, new=%d, old=%d\n",
		__func__, __LINE__, current_pll, new_ratio, old_ratio);

	/*
	 * Convert to halves
	 */
	if (new_ratio > 20)
		new_ratio = (new_ratio-10)<<1;
	if (old_ratio > 20)
		old_ratio = (old_ratio-10)<<1;

	/*
	 * Make sure that we never shorten the sleep values
	 */
	if (new_ratio > old_ratio) {
		if (newPLL&PLL_DO_LPJ)
			pllUpdateLPJ(masked_boot_ratio, new_ratio, boot_loops);

		pr_debug(__FILE__">%s()-%d:  masked_boot_ratio=%d, new_ratio="
		"%d, boot_loops=%ld, loops_per_jiffy=%ld\n", __func__, __LINE__,
		masked_boot_ratio, new_ratio, boot_loops, loops_per_jiffy);

		set_PLL(current_pll);
	} else {
		pr_debug(__FILE__">%s()-%d:  masked_boot_ratio=%d, new_"
			"ratio=%d, boot_loops=%ld, loops_per_jiffy=%ld\n",
			__func__, __LINE__, masked_boot_ratio, new_ratio,
			boot_loops, loops_per_jiffy);

		set_PLL(current_pll);

		if (newPLL&PLL_DO_LPJ)
			pllUpdateLPJ(masked_boot_ratio, new_ratio, boot_loops);

		pr_debug(__FILE__">%s()-%d:  masked_boot_ratio=%d, new_"
			"ratio=%d, boot_loops=%ld, loops_per_jiffy=%ld\n",
			__func__, __LINE__, masked_boot_ratio, new_ratio,
			boot_loops, loops_per_jiffy);
	}

	raw_notifier_call_chain(&pllifvPllSwitchChain, pllifmPllSwitch,
		pllifvSwitchCallData.data);

	/*
	 * This is used to print the clock frequency in /proc/cpuinfo
	 */
	ppc_proc_freq = pllifCfgToFreq(new_ratio_cp);
	pr_debug(__FILE__">%s()-%d:  pllifCfgToFreq(%u)=%lu\n", __func__,
		__LINE__, new_ratio_cp, ppc_proc_freq);

#if 0
	save_flags(flags);
	cli();

	loops_per_jiffy = pllNewLPJ(masked_boot_ratio, new_ratio, boot_loops);

	set_PLL(current_pll);

	restore_flags(flags);
#endif
}

#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_HRTIMER
static enum hrtimer_restart pllTimerF(struct hrtimer *hrt)
{
#ifdef DEBUG
cycles_t now;
cycles_t usec, tmp, cntlz, cnttz;

	now = get_cycles();

	now = now-pll_time_stamp;

	/*
	 * Aw cmon, I'm just havin' a little fun with PPC assembly.
	 * Just wish I could find a way to use an rlwinm ...
	 */
	cnttz = tb_ticks_per_sec; /* needed to get the assembly to ...
				 * look right ... ??? */
	tmp = now*15625;

	__asm__ __volatile__ (
		"addi %0,%3,-1\n"
		"andc %1,%3,%0\n"
		"cntlzw %1,%1\n"
		"subfic %1,%1,31\n"
		"cntlzw %0,%2\n":
		"=r"(cntlz), "=r"(cnttz):
		"r"(tmp), "b"(cnttz)
	);

	/*
	 * 1,000,000 usec per sec and 1,000,000 is 15625<<6
	 */
	usec = ((tmp<<cntlz)/(tb_ticks_per_sec>>cnttz))<<6;
	usec = (usec+(1UL<<(cntlz+cnttz-1)))>>(cntlz+cnttz);

	pr_debug(__FILE__">%s()-%d:  Time delta is %lu cycles, "
		"%lu uS (cntlz=%lu, cnttz=%lu)\n", __func__, __LINE__, now,
		usec, cntlz, cnttz);
#endif
	raw_notifier_call_chain(&pllifvPllLockChain, pllifmPllLock,
		pllifvLockCallData.data);

	/*
	 * Clear all lock bits
	 */
	boot_ratio &= ~(PLL_TIMER|PLL0_LOCK|PLL1_LOCK);

	if ((unsigned int) hrtimers_got_no_freakin_callback_data)
		pllifiSwitchPLLs((unsigned int)
			hrtimers_got_no_freakin_callback_data);

	return HRTIMER_NORESTART;
}
#else
static void pllTimerF(unsigned long newPLL)
{
cycles_t now;

	now = get_cycles();
	now = now-pll_time_stamp;

#ifdef DEBUG
	{
	cycles_t usec, tmp, cntlz, cnttz;

		/*
		 * Aw cmon, I'm just havin' a little fun with PPC assembly.
		 * Just wish I could find a way to use an rlwinm ...
		 */
		cnttz = tb_ticks_per_sec; /* needed to get the assembly to ...
					 * look right ... ??? */
#define MULFIRST
#ifdef MULFIRST
		tmp = now*15625;
#else
		tmp = now;
#endif

		__asm__ __volatile__ (
			"addi %0,%3,-1\n"
			"andc %1,%3,%0\n"
			"cntlzw %1,%1\n"
			"subfic %1,%1,31\n"
			"cntlzw %0,%2\n":
			"=r"(cntlz), "=r"(cnttz):
			"r"(tmp), "b"(cnttz)
		);

		/*
		 * 1,000,000 usec per sec and 1,000,000 is 15625<<6
		 */
//		usec = (((now<<cntlz)/(tb_ticks_per_sec>>cnttz)*15625)+(1UL<<
//			(cntlz+cnttz-1-6)))>>(cntlz+cnttz-6);
#ifdef MULFIRST
		usec = ((tmp<<cntlz)/(tb_ticks_per_sec>>cnttz))<<6;
		usec = (usec+(1UL<<(cntlz+cnttz-1)))>>(cntlz+cnttz);
#else
		usec = ((tmp<<cntlz)/(tb_ticks_per_sec>>cnttz)*15625)<<6;
		usec = (usec+(1UL<<(cntlz+cnttz-1)))>>(cntlz+cnttz);
#endif

		pr_debug(__FILE__">%s()-%d:  Time delta is %lu cycles, "
			"%lu uS (cntlz=%lu, cnttz=%lu)\n", __func__, __LINE__,
			now, usec, cntlz, cnttz);
	}
#endif
	/*
	 * Make sure it has been at least 100 usec. 100 usec is 100 *
	 * tb_ticks_per_sec / 1,000,000 cycles, so:
	 *	if(now<100*tb_ticks_per_sec/1000000
	 * 1,000,000 is 15625<<6, so:
	 *	if((now<<6)<100*tb_ticks_per_sec/15625)
	 * 100 is 25<<2, so:
	 *	if((now<<4)<25*tb_ticks_per_sec/15625)
	 * 15625 is 3125*5, so:
	 *	if((now<<4)*5<25*tb_ticks_per_sec/3125)
	 * obviously 25/3125 -> 1/125:
	 *	if((now<<4)*5<tb_ticks_per_sec/125)
	 */
	if ((now<<4)*5 < tb_ticks_per_sec/125)
		udelay(100-now*1000000/tb_ticks_per_sec);

	raw_notifier_call_chain(&pllifvPllLockChain, pllifmPllLock,
		pllifvLockCallData.data);

	/*
	 * Clear all lock bits
	 */
	boot_ratio &= ~(PLL_TIMER|PLL0_LOCK|PLL1_LOCK);

	if ((unsigned int)newPLL)
		pllifiSwitchPLLs((unsigned int)newPLL);
}
#endif

/*
 * Handle accesses to the pll register. Examples for write:
 *	  value		CFGx/RNGx/res	   effect
 *	0x08010000			switch to PLL1
 *	0x08000000			switch to PLL0
 *	0xc000fa00	1111 1/01/0	PLL0 off (CFG/RNG 31/1)
 *	0xc000f200	1111 0/01/0	PLL0 to 20x (CFG/RNG 30/1)
 *	0x30000004	0000 0/10/0	PLL1 off (CFG/RNG 0/2)
 *	0x30000054	0101 0/10/0	PLL1 to 5x (CFG/RNG a/2)
 */
/**
 * modifyPLL: - Takes steps to modify PLL as requested
 * @pll: Specifies the new value and desired operation to be performed
 * @scaleLPJ: flag to indicate whether to scale the loops_per_jiffy value
 *
 * Based on the value passed in the pll argument, this takes the steps necessary
 * to change the PLL as requested. The upper 7 or 8 bits of the PLL are read
 * only. These bit positions in the pll argument are used to specify flags that
 * indicate the validity of the other fields in the pll argument. See the
 * pll_if.h header for detail and actual values.
 */
int modifyPLL(unsigned int pll, int scaleLPJ)
{
unsigned int current_pll, work_mask, pll_x;
int rval = 0;

	pr_debug(__FILE__">%s()-%d:\n", __func__, __LINE__);
	pr_debug(__FILE__">%s()-%d:  pll=0x%08x\n", __func__, __LINE__, pll);

	/*
	 * This is not reentrant
	 */
	if (test_and_set_bit(PLL_LOCK_BIT, (unsigned long *)&boot_ratio)) {
		pr_debug(__FILE__">%s()-%d:  Busy!\n", __func__, __LINE__);
		return -EAGAIN;
	}

	/*
	 * Don't allow any changes if a timer is pending
	 */
	if (test_bit(PLL_TIMER_BIT, (unsigned long *)&boot_ratio))
		goto checkPLLBusy;

	current_pll = get_PLL();
	work_mask = pll>>24;

	/*
	 * Check to see if the currently selected PLL is being modified
	 */
	pll_x = get_active_PLL(current_pll);

	if ((pll_x == 0 && work_mask&(PLL0_DO_CFG|PLL0_DO_RNG|PLL0_DO_CONTROL))
		|| (pll_x == 1 && work_mask&(PLL1_DO_CFG|PLL1_DO_RNG)))
		goto checkPLLInVal;

	/*
	 * Can't change to a PLL that is off. Also can't immediately change to
	 * one that is not locked. Catch that supposedly impossible condition.
	 */
	if (work_mask&PLL_DO_SEL) {
	int next_ratio;
	unsigned int which_config;

		pll_x = get_next_PLL(pll);

		/*
		 * Figure out where the next ratio comes from. It will be from
		 * pll if we are changing the next pll and current_pll if not.
		 */
		which_config = pll_x?((work_mask&PLL1_DO_CFG)?pll:current_pll):
			((work_mask&PLL0_DO_CFG)?pll:current_pll);
		next_ratio = get_PLL_ratio(pll_x, which_config);
		if (next_ratio < 4 || next_ratio > 30)
			goto checkPLLInVal;

		pll_x = ((pll_x == 0 && boot_ratio&PLL0_LOCK) || (pll_x == 1 &&
			boot_ratio&PLL1_LOCK))?1:0;

	}
	/*
	 * To avoid complications, don't allow both plls to be half ratios
	 */
	if (work_mask&PLL0_DO_CFG) {
	int old_ratio1, new_ratio0;

		old_ratio1 = get_PLL_ratio(1, current_pll);
		new_ratio0 = get_PLL_ratio(0, pll);

		if (old_ratio1 > 4 && old_ratio1 < 20 && new_ratio0 > 4 &&
			new_ratio0 < 20 && (old_ratio1&0x1) & (new_ratio0&0x1))
			goto checkPLLInVal;
	} else if (work_mask&PLL1_DO_CFG) {
	int old_ratio0, new_ratio1;

		old_ratio0 = get_PLL_ratio(0, current_pll);
		new_ratio1 = get_PLL_ratio(1, pll);

		if (old_ratio0 > 4 && old_ratio0 < 20 && new_ratio1 > 4 &&
			new_ratio1 < 20 && (old_ratio0&0x1) & (new_ratio1&0x1))
			goto checkPLLInVal;
	}

	/*
	 * Determine if we will need to schedule a timer for a PLL relock. If
	 * any PLL config is being changed then a timer will be needed. Also
	 * need one if changing to a PLL that is not locked, though that should
	 * not happen.
	 */
	if ((work_mask&(PLL0_DO_CFG|PLL0_DO_RNG|PLL1_DO_CFG|PLL1_DO_RNG|
		PLL0_DO_CONTROL)) || (work_mask&PLL_DO_SEL && pll_x)) {
	unsigned int pll_mask, temp;

		pll_mask = 0;

		if (work_mask&PLL0_DO_CFG) {
			pll_mask |= PLL0_CFG_MASK;

			/*
			 * Flag that PLL0 needs to relock
			 */
			boot_ratio |= PLL0_LOCK;
		}

		if (work_mask&PLL0_DO_RNG)
			pll_mask |= PLL0_RNG_MASK;

		if (work_mask&PLL1_DO_CFG) {
			pll_mask |= PLL1_CFG_MASK;

			/*
			 * Flag that PLL1 needs to relock
			 */
			boot_ratio |= PLL1_LOCK;
		}

		if (work_mask&PLL1_DO_RNG)
			pll_mask |= PLL1_RNG_MASK;

		temp = (current_pll&~pll_mask)|(pll&pll_mask);

		if (pll_mask)
			set_PLL(temp);

		/*
		 * Flag that a timer is pending
		 */
		boot_ratio |= PLL_TIMER;

		/*
		 * Schedule a timer to clear the PLL lock bits (and signal that
		 * it is ok to select the PLL)
		 */
#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_HRTIMER
		/*
		 * Oh please, someone tell me I'm just to stupid to know how
		 * to pass this to the timer function!
		 */
		hrtimers_got_no_freakin_callback_data = (work_mask&PLL_DO_SEL)?
			(PLL_DO_SEL<<24)|(scaleLPJ?PLL_DO_LPJ:0)|pll&
			PLL_SEL_MASK:0;

		pll_timer.expires = ktime_set(0, 100000);

		hrtimer_start(&pll_timer, pll_timer.expires, HRTIMER_MODE_REL);
#ifdef DEBUG
		pll_time_stamp = get_cycles();
#endif
#else
		/*
		 * We might want to pass three pieces of data to the timer
		 *   i)	that we want to switch PLLs (PLL_DO_SEL)
		 *  ii)	which PLL to switch to (PLL_SEL_MASK)
		 * iii) flag to control whether loops_per_jiffy is updated
		 *      (PLL_DO_LPJ)
		 */
		pll_timer.data = (work_mask&PLL_DO_SEL)?(PLL_DO_SEL<<24)|(
			scaleLPJ?PLL_DO_LPJ:0)|(pll&PLL_SEL_MASK):0;

		/*
		 * Relock takes 100 us. See how many jiffies will take care of
		 * it.
		 */
		pll_timer.expires = (100*HZ/1000000);
		if (pll_timer.expires == 0)
			pll_timer.expires = 1;

		pll_timer.expires = jiffies+pll_timer.expires;
		add_timer(&pll_timer);

		pll_time_stamp = get_cycles();
#endif
	} else if (work_mask&PLL_DO_SEL)
		pllifiSwitchPLLs(pll|(scaleLPJ?PLL_DO_LPJ:0));

checkPLLOut:
	clear_bit(PLL_LOCK_BIT, (unsigned long *)&boot_ratio);

	return rval;
checkPLLBusy:
	rval = -EBUSY;
	goto checkPLLOut;
checkPLLInVal:
	rval = -EINVAL;
	goto checkPLLOut;
}
EXPORT_SYMBOL(modifyPLL);

/**
 * pllifCFgToFreq: - Takes a ratio and returns the frequency
 * @cfg: The PLL ratio field value
 *
 * This takes a PLL ratio field value and uses it along with the bus frequency
 * to compute the processor frequency.
 */
unsigned int pllifCfgToFreq(unsigned int cfg)
{
	return (cfg < 21?cfg>>1:cfg-10)*pllifvBusClock;
}
EXPORT_SYMBOL(pllifCfgToFreq);

#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_CPU_FREQ
/**
 * pllifGetBusClock: - Returns the bus frequency
 *
 * This returns the determined bus frequency in Hz.
 */
unsigned int pllifGetBusClock()
{
	return pllifvBusClock;
}
EXPORT_SYMBOL(pllifGetBusClock);

/**
 * pllifRegisterPllSwitchCB: - Registers a pll switch call back
 * @nb: structure describing the call back to register
 *
 * This registers a call back function that will be called when the clock is
 * switched from one PLL to the other.
 */
int pllifRegisterPllSwitchCB(struct notifier_block *nb)
{
int ret;

	pllifvSwitchCallData.data = (void *)nb->next;
	nb->next = NULL;

	pllifvSwitchCallData.scalar = nb->priority;
	nb->priority = 0;

	ret = raw_notifier_chain_register(&pllifvPllSwitchChain, nb);

	return ret;
}
EXPORT_SYMBOL(pllifRegisterPllSwitchCB);

/**
 * pllifUnregisterPllSwitchCB: - Cancels a previously registered call back
 * @nb: structure describing the call back to cancel
 *
 * This cancels a previously registered switch call back
 */
void pllifUnregisterPllSwitchCB(struct notifier_block *nb)
{

	raw_notifier_chain_unregister(&pllifvPllSwitchChain, nb);
}
EXPORT_SYMBOL(pllifUnregisterPllSwitchCB);

/**
 * pllifRegisterPllLockCB: - Registers a pll lock call back
 * @nb: structure describing the call back to register
 *
 * This registers a call back function that will be called when a PLL has
 * locked to a new frequency.
 */
int pllifRegisterPllLockCB(struct notifier_block *nb)
{
int ret;

	pllifvLockCallData.data = (void *)nb->next;
	nb->next = NULL;

	pllifvLockCallData.scalar = nb->priority;
	nb->priority = 0;

	ret = raw_notifier_chain_register(&pllifvPllLockChain, nb);

	return ret;
}
EXPORT_SYMBOL(pllifRegisterPllLockCB);

/**
 * pllifUnregisterPllLockCB: - Cancels a previously registered call back
 * @nb: structure describing the call back to cancel
 *
 * This cancels a previously registered PLL lock call back
 */
void pllifUnregisterPllLockCB(struct notifier_block *nb)
{

	raw_notifier_chain_unregister(&pllifvPllLockChain, nb);
}
EXPORT_SYMBOL(pllifUnregisterPllLockCB);
#endif

module_param(override_bus_clock, uint, 0644);
MODULE_PARM_DESC(override_bus_clock,
	"Bus clock frequency in KHz used to compute core clock frequency from"
	" bus ratios.");