Add low level PLL config register interface module
PATCH v2 1/5 Add low level PLL config register interface module
This adds a small module to handle the low level details of dealing with the PLL config register (HID1) found in the IBM 750GX. It provides 2 possible interfaces, both selectable via kernel config options. One is a sysfs attribute and the other is a normal function API. It is called pll_if.
The determination of the bus frequency is what worked on a PowerMac 8600. Any suggestions on a more general solution are welcome.
After receiving comments, I added an argument for the data item to the notifier register functions exported for the cpufreq API.
I also fixed a deadlock if the module is unloaded before _modify_PLL() is ever called.
My name is Kevin Diggs and I approve this patch.
powerpc/kernel/cpu/pll_if.c new file +807 -0
@@ -0,0 +1,807 @@ +/* +* pll_if.c - low level interface to HID1 (PLL config) in the PowerPC 750GX +* +* 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. +* +* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ +#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" +#include "linux/completion.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/time.h" +#include <asm/mmu.h> +#include <asm/processor.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 DECLARE_COMPLETION (pllif_exit_completion); + +static unsigned int boot_ratio; + +static unsigned int busclock =0 ; +module_param (busclock, uint,0 ); +MODULE_PARM_DESC (busclock, +"Bus clock frequency in KHz used to compute core clock frequency from" +" bus ratios." ); + +static unsigned int pllif_bus_clock; + +#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_HRTIMER +static enum hrtimer_restart pllif_i_timer_f (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 pllif_i_timer_f (unsigned long newPLL); +static struct timer_list pll_timer; +cycles_t pll_time_stamp; +#endif + +#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS + +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 ()); +} + +static ssize_t __used store_ppc750gxpll (struct sys_device *dev, +const char *buf,size_t count) +{ +unsigned long pll_ul; +int ret; + +pr_debug (__FILE__">%s()-%d: buf=%s, count=%d\n" , __func__, __LINE__, + buf, count); + + ret =strict_strtoul (buf,16 , &pll_ul); + +pr_debug (__FILE__">%s()-%d: strict_strtoul() returns %d\n" , __func__, + __LINE__, ret); +pr_debug (__FILE__">%s()-%d: %lx (%lu)\n" , __func__, __LINE__, pll_ul, + pll_ul); + +if (!ret) { + ret = count; + +/* pllif_modify_PLL((unsigned int)pll_ul,!0); */ +pllif_modify_PLL ((unsigned int )pll_ul,0 ); + } + +return ret; +} + +static SYSDEV_ATTR(ppc750gxpll, 0600, show_ppc750gxpll, store_ppc750gxpll); +#endif + +#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_CPU_FREQ +struct pllif_call_data_t { +void *data; +int scalar; +}; + +static struct pllif_call_data_t pllif_switch_call_data; +static struct pllif_call_data_t pllif_lock_call_data; +static RAW_NOTIFIER_HEAD (pllif_pll_switch_chain); +static RAW_NOTIFIER_HEAD (pllif_pll_lock_chain); +#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 (busclock) + pllif_bus_clock = busclock*1000 ; + +#ifdef CONFIG_PPC_OF +/* +* If bus clock is not specified, try to get it via OF +*/ +if (!pllif_bus_clock) { +/* +* 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) + pllif_bus_clock = (unsigned int ) *clk; + +of_node_put (tree_root); + +pr_debug (__FILE__">%s()-%d: Bus clock from OF is %u\n" , + __func__, __LINE__, pllif_bus_clock); + } + } +#endif /* CONFIG_PPC_OF */ + +if (!pllif_bus_clock) { +pr_err (__FILE__">%s()-%d: Can't determine bus clock.\n" , + __func__, __LINE__); + +return -EINVAL ; + } + +/* +* Make sure the clock frequency is correct +*/ + temp =get_PLL (); + temp =get_PLL_ratio (get_active_PLL (temp), temp); + + ppc_proc_freq =pllif_cfg_to_freq (temp); + +#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS +/* +* 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 = pllif_i_timer_f; + +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. The test_bit() condition is to catch the case where +* we exit with the pllif_modify_PLL() routine never having been called. +*/ +if (test_bit (PLL_LOCK_BIT , (unsigned long *)&boot_ratio)) +wait_for_completion (&pllif_exit_completion); +} + +module_init (init_PLL); +module_exit (exit_PLL); + +#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_SYSFS +static unsigned long pllif_i_new_LPJ (unsigned int oldRatio,unsigned int + newRatio,unsigned long LPJ ) +{ +if (LPJ >200000000 ) +return LPJ /oldRatio*newRatio; +else +return LPJ *newRatio/oldRatio; +} + +static inline void pllif_i_update_LPJ (unsigned int oldRatio,unsigned int + newRatio,unsigned long LPJ ) +{ + loops_per_jiffy =pllif_i_new_LPJ (oldRatio, newRatio,LPJ ); +} +#else +#define pllif_i_update_LPJ (a, b, c) +#endif + +static void pllif_i_switch_PLLs (unsigned int newPLL) +{ +#if 0 +unsigned long flags; +#endif +unsigned int new_ratio; +unsigned int new_ratio_cp; +unsigned int old_ratio; +unsigned int current_pll; +unsigned int 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 ) +pllif_i_update_LPJ (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 ) +pllif_i_update_LPJ (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 (&pllif_pll_switch_chain, pllifmPllSwitch, + pllif_switch_call_data.data); + +/* +* This is used to print the clock frequency in /proc/cpuinfo +*/ + ppc_proc_freq =pllif_cfg_to_freq (new_ratio_cp); +pr_debug (__FILE__">%s()-%d: pllif_cfg_to_freq(%u)=%lu\n" , __func__, + __LINE__, new_ratio_cp, ppc_proc_freq); + +#if 0 +save_flags (flags); +cli (); + + loops_per_jiffy =pllif_i_new_LPJ (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 pllif_i_timer_f (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 ... (or the equally +* fun rlwnm). This gets the leading zeros (for the dividend) and the +* trailing zeros (for the divisor) to try to preserve some precision +* after the big divide. +*/ + cnttz = tb_ticks_per_sec;/* needed to get the assembly to ... +* look right ... ??? */ + tmp = now*15625 ; + + asm ( +"addi %0,%3,-1\n\t" +"andc %1,%3,%0\n\t" +"cntlzw %1,%1\n\t" +"subfic %1,%1,31\n\t" +"cntlzw %0,%2\n\t" : +"=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 (&pllif_pll_lock_chain, pllifmPllLock, + pllif_lock_call_data.data); + +/* +* Clear all lock bits +*/ + boot_ratio &= ~(PLL_TIMER |PLL0_LOCK |PLL1_LOCK ); + +if ((unsigned int ) hrtimers_got_no_freakin_callback_data) +pllif_i_switch_PLLs ((unsigned int ) + hrtimers_got_no_freakin_callback_data); + +complete (&pllif_exit_completion); + +return HRTIMER_NORESTART ; +} +#else +static void pllif_i_timer_f (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 ... (or the +* equally fun rlwnm). This gets the leading zeros (for the +* dividend) and the trailing zeros (for the divisor) to try to +* preserve some precision after the big divide. +*/ + 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 ( +"addi %0,%3,-1\n\t" +"andc %1,%3,%0\n\t" +"cntlzw %1,%1\n\t" +"subfic %1,%1,31\n\t" +"cntlzw %0,%2\n\t" : +"=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 (&pllif_pll_lock_chain, pllifmPllLock, + pllif_lock_call_data.data); + +/* +* Clear all lock bits +*/ + boot_ratio &= ~(PLL_TIMER |PLL0_LOCK |PLL1_LOCK ); + +if ((unsigned int )newPLL) +pllif_i_switch_PLLs ((unsigned int )newPLL); + +complete (&pllif_exit_completion); +} +#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 pllif_modify_PLL (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; + +INIT_COMPLETION (pllif_exit_completion); + + 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 ) { +pllif_i_switch_PLLs (pll|(scaleLPJ?PLL_DO_LPJ :0 )); +complete (&pllif_exit_completion); + } + +checkPLLOut: +clear_bit (PLL_LOCK_BIT , (unsigned long *)&boot_ratio); + +return rval; +checkPLLBusy: + rval = -EBUSY ; + goto checkPLLOut; +checkPLLInVal: + rval = -EINVAL ; +complete (&pllif_exit_completion); + goto checkPLLOut; +} +EXPORT_SYMBOL (pllif_modify_PLL); + +/** +* pllif_cFg_to_freq: - 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 pllif_cfg_to_freq (unsigned int cfg) +{ +return (cfg <21 ?cfg>>1 :cfg-10 )*pllif_bus_clock; +} +EXPORT_SYMBOL (pllif_cfg_to_freq); + +#ifdef CONFIG_PPC_750GX_DUAL_PLL_IF_CPU_FREQ +/** +* pllif_get_bus_clock: - Returns the bus frequency +* +* This returns the determined bus frequency in Hz. +*/ +unsigned int pllif_get_bus_clock () +{ +return pllif_bus_clock; +} +EXPORT_SYMBOL (pllif_get_bus_clock); + +/** +* pllif_register_pll_switch_cb: - Registers a pll switch call back +* @nb: structure describing the call back to register +* @data: data to be passed to the callback function +* +* This registers a call back function that will be called when the clock is +* switched from one PLL to the other. The call back routine should be set in +* the notifier_call member of @nb. +*/ +int pllif_register_pll_switch_cb (struct notifier_block *nb,void *data) +{ +int ret; + + pllif_switch_call_data.data = data; + + ret =raw_notifier_chain_register (&pllif_pll_switch_chain, nb); + +return ret; +} +EXPORT_SYMBOL (pllif_register_pll_switch_cb); + +/** +* pllif_unregister_pll_switch_cb: - Cancels a previously registered call back +* @nb: structure describing the call back to cancel +* +* This cancels a previously registered switch call back +*/ +void pllif_unregister_pll_switch_cb (struct notifier_block *nb) +{ +raw_notifier_chain_unregister (&pllif_pll_switch_chain, nb); +} +EXPORT_SYMBOL (pllif_unregister_pll_switch_cb); + +/** +* pllif_register_pll_lock_cb: - Registers a pll lock call back +* @nb: structure describing the call back to register +* @data: data to be passed to the callback function +* +* This registers a call back function that will be called when a PLL has +* locked to a new frequency. The call back routine should be set in the +* notifier_call member of @nb. +*/ +int pllif_register_pll_lock_cb (struct notifier_block *nb,void *data) +{ +int ret; + + pllif_lock_call_data.data = data; + + ret =raw_notifier_chain_register (&pllif_pll_lock_chain, nb); + +return ret; +} +EXPORT_SYMBOL (pllif_register_pll_lock_cb); + +/** +* pllif_unregister_pll_lock_cb: - Cancels a previously registered call back +* @nb: structure describing the call back to cancel +* +* This cancels a previously registered PLL lock call back +*/ +void pllif_unregister_pll_lock_cb (struct notifier_block *nb) +{ +raw_notifier_chain_unregister (&pllif_pll_lock_chain, nb); +} +EXPORT_SYMBOL (pllif_unregister_pll_lock_cb); +#endif
asm-powerpc/pll.h new file +209 -0
209 lines not on this page · raw message
asm-powerpc/pll_if.h new file +117 -0
117 lines not on this page · raw message