/* * 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 #include "linux/kernel.h" #include #include #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 #include #include "asm/time.h" #include #include #include #include #include #include #include #include #include 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<>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<>cnttz)*15625)+(1UL<< // (cntlz+cnttz-1-6)))>>(cntlz+cnttz-6); #ifdef MULFIRST usec = ((tmp<>cnttz))<<6; usec = (usec+(1UL<<(cntlz+cnttz-1)))>>(cntlz+cnttz); #else usec = ((tmp<>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%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.");