Message ID | 1364994565-16010-8-git-send-email-chenhui.zhao@freescale.com (mailing list archive) |
---|---|
State | Superseded, archived |
Delegated to: | Kumar Gala |
Headers | show |
Also send this patch to cpufreq@vger.kernel.org and linux-pm@vger.kernel.org And better to rebase it on git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git Thanks, Yuantian > -----Original Message----- > From: Linuxppc-dev [mailto:linuxppc-dev- > bounces+b29983=freescale.com@lists.ozlabs.org] On Behalf Of Zhao Chenhui > Sent: 2013年4月3日 21:09 > To: linuxppc-dev@lists.ozlabs.org > Subject: [PATCH 08/17] powerpc/85xx: add support to JOG feature using > cpufreq interface > > From: chenhui zhao <chenhui.zhao@freescale.com> > > Some MPC85xx SoCs like MPC8536 and P1022 have a JOG feature, which > provides a dynamic mechanism to lower or raise the CPU core clock at > runtime. > > This patch adds the support to change CPU frequency using the standard > cpufreq interface. The ratio CORE to CCB can be 1:1(except MPC8536), 3:2, > 2:1, 5:2, 3:1, 7:2 and 4:1. > > Two CPU cores on P1022 must not in the low power state during the > frequency transition. The driver uses a flag to meet the requirement. > > The jog mode frequency transition process on the MPC8536 is similar to > the deep sleep process. The driver need save the CPU state and restore it > after CPU warm reset. > > Note: > * The I/O peripherals such as PCIe and eTSEC may lose packets during > the jog mode frequency transition. > * The driver doesn't support MPC8536 Rev 1.0 due to a JOG erratum. > Subsequent revisions of MPC8536 have corrected the erratum. > > Signed-off-by: Dave Liu <daveliu@freescale.com> > Signed-off-by: Li Yang <leoli@freescale.com> > Signed-off-by: Jerry Huang <Chang-Ming.Huang@freescale.com> > Signed-off-by: Zhao Chenhui <chenhui.zhao@freescale.com> > --- > arch/powerpc/platforms/85xx/Makefile | 1 + > arch/powerpc/sysdev/fsl_soc.h | 5 + > drivers/cpufreq/Kconfig.powerpc | 10 + > drivers/cpufreq/Makefile | 1 + > drivers/cpufreq/cpufreq-jog.c | 416 > ++++++++++++++++++++++++++++++++++ > include/linux/cpu.h | 4 + > kernel/cpu.c | 60 +++--- > 7 files changed, 467 insertions(+), 30 deletions(-) create mode 100644 > drivers/cpufreq/cpufreq-jog.c > > diff --git a/arch/powerpc/platforms/85xx/Makefile > b/arch/powerpc/platforms/85xx/Makefile > index 2f4713f..4946be7 100644 > --- a/arch/powerpc/platforms/85xx/Makefile > +++ b/arch/powerpc/platforms/85xx/Makefile > @@ -3,6 +3,7 @@ > # > obj-$(CONFIG_SMP) += smp.o > obj-$(CONFIG_FSL_PMC) += sleep.o > +obj-$(CONFIG_MPC85xx_CPUFREQ) += sleep.o > > obj-y += common.o > > diff --git a/arch/powerpc/sysdev/fsl_soc.h > b/arch/powerpc/sysdev/fsl_soc.h index 29a87ee..b7d5ef7 100644 > --- a/arch/powerpc/sysdev/fsl_soc.h > +++ b/arch/powerpc/sysdev/fsl_soc.h > @@ -62,5 +62,10 @@ void fsl_hv_halt(void); > * code can be compatible with both 32-bit & 36-bit. > */ > extern void mpc85xx_enter_deep_sleep(u64 ccsrbar, u32 powmgtreq); > + > +static inline void mpc85xx_enter_jog(u64 ccsrbar, u32 powmgtreq) { > + mpc85xx_enter_deep_sleep(ccsrbar, powmgtreq); } > #endif > #endif > diff --git a/drivers/cpufreq/Kconfig.powerpc > b/drivers/cpufreq/Kconfig.powerpc index e76992f..c47a662 100644 > --- a/drivers/cpufreq/Kconfig.powerpc > +++ b/drivers/cpufreq/Kconfig.powerpc > @@ -5,3 +5,13 @@ config CPU_FREQ_MAPLE > help > This adds support for frequency switching on Maple 970FX > Evaluation Board and compatible boards (IBM JS2x blades). > + > +config MPC85xx_CPUFREQ > + bool "Support for Freescale MPC85xx CPU freq" > + depends on PPC_85xx && PPC32 && !PPC_E500MC > + select CPU_FREQ_TABLE > + help > + This adds support for dynamic frequency switching on > + Freescale MPC85xx by cpufreq interface. MPC8536 and P1022 > + have a JOG feature, which provides a dynamic mechanism > + to lower or raise the CPU core clock at runtime. > diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index > 863fd18..628fa0e 100644 > --- a/drivers/cpufreq/Makefile > +++ b/drivers/cpufreq/Makefile > @@ -61,3 +61,4 @@ obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q- > cpufreq.o > > ######################################################################### > ######### > # PowerPC platform drivers > obj-$(CONFIG_CPU_FREQ_MAPLE) += maple-cpufreq.o > +obj-$(CONFIG_MPC85xx_CPUFREQ) += cpufreq-jog.o > diff --git a/drivers/cpufreq/cpufreq-jog.c b/drivers/cpufreq/cpufreq- > jog.c new file mode 100644 index 0000000..5656d48 > --- /dev/null > +++ b/drivers/cpufreq/cpufreq-jog.c > @@ -0,0 +1,416 @@ > +/* > + * Copyright (C) 2008-2012 Freescale Semiconductor, Inc. > + * Author: Dave Liu <daveliu@freescale.com> > + * Modifier: Chenhui Zhao <chenhui.zhao@freescale.com> > + * > + * The cpufreq driver is for Freescale 85xx processor, > + * based on arch/powerpc/platforms/cell/cbe_cpufreq.c > + * (C) Copyright IBM Deutschland Entwicklung GmbH 2005-2007 > + * Christian Krafft <krafft@de.ibm.com> > + * > + * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. > + */ > + > +#include <linux/module.h> > +#include <linux/cpufreq.h> > +#include <linux/of_platform.h> > +#include <linux/suspend.h> > +#include <linux/cpu.h> > +#include <linux/io.h> > +#include <linux/time.h> > +#include <linux/smp.h> > + > +#include <asm/prom.h> > +#include <asm/reg.h> > +#include <asm/machdep.h> > + > +#include <sysdev/fsl_soc.h> > + > +static DEFINE_MUTEX(mpc85xx_switch_mutex); > +static void __iomem *guts; > + > +static u32 sysfreq; > +static unsigned int max_pll[2]; > +static atomic_t in_jog_process; > +static struct cpufreq_frequency_table *mpc85xx_freqs; static int > +(*set_pll)(unsigned int cpu, unsigned int pll); > + > +static struct cpufreq_frequency_table mpc8536_freqs_table[] = { > + {3, 0}, > + {4, 0}, > + {5, 0}, > + {6, 0}, > + {7, 0}, > + {8, 0}, > + {0, CPUFREQ_TABLE_END}, > +}; > + > +static struct cpufreq_frequency_table p1022_freqs_table[] = { > + {2, 0}, > + {3, 0}, > + {4, 0}, > + {5, 0}, > + {6, 0}, > + {7, 0}, > + {8, 0}, > + {0, CPUFREQ_TABLE_END}, > +}; > + > +#define FREQ_500MHz 500000000 > +#define FREQ_800MHz 800000000 > + > +#define CORE_RATIO_STRIDE 8 > +#define CORE_RATIO_MASK 0x3f > +#define CORE_RATIO_SHIFT 16 > + > +#define PORPLLSR 0x0 /* Power-On Reset PLL ratio status register */ > + > +#define PMJCR 0x7c /* Power Management Jog Control Register */ > +#define PMJCR_CORE0_SPD 0x00001000 > +#define PMJCR_CORE_SPD 0x00002000 > + > +#define POWMGTCSR 0x80 /* Power management control and status > register */ > +#define POWMGTCSR_JOG 0x00200000 > +#define POWMGTCSR_INT_MASK 0x00000f00 > + > +static void spin_while_jogging(void *dummy) { > + unsigned long flags; > + > + local_irq_save(flags); > + > + atomic_inc(&in_jog_process); > + > + while (atomic_read(&in_jog_process) != 0) > + barrier(); > + > + local_irq_restore(flags); > +} > + > +static int get_pll(int hw_cpu) > +{ > + int shift; > + u32 val = in_be32(guts + PORPLLSR); > + > + shift = hw_cpu * CORE_RATIO_STRIDE + CORE_RATIO_SHIFT; > + > + return (val >> shift) & CORE_RATIO_MASK; } > + > +static int mpc8536_set_pll(unsigned int cpu, unsigned int pll) { > + u32 corefreq, val, mask; > + unsigned int cur_pll = get_pll(0); > + unsigned long flags; > + > + if (pll == cur_pll) > + return 0; > + > + val = (pll & CORE_RATIO_MASK) << CORE_RATIO_SHIFT; > + > + corefreq = sysfreq * pll / 2; > + /* > + * Set the COREx_SPD bit if the requested core frequency > + * is larger than the threshold frequency. > + */ > + if (corefreq > FREQ_800MHz) > + val |= PMJCR_CORE_SPD; > + > + mask = (CORE_RATIO_MASK << CORE_RATIO_SHIFT) | PMJCR_CORE_SPD; > + clrsetbits_be32(guts + PMJCR, mask, val); > + > + /* readback to sync write */ > + in_be32(guts + PMJCR); > + > + local_irq_save(flags); > + mpc85xx_enter_jog(get_immrbase(), POWMGTCSR_JOG); > + local_irq_restore(flags); > + > + /* verify */ > + cur_pll = get_pll(0); > + if (cur_pll != pll) { > + pr_err("%s: error. The current PLL of core 0 is %d instead > of %d.\n", > + __func__, cur_pll, pll); > + return -1; > + } > + > + return 0; > +} > + > +static int p1022_set_pll(unsigned int cpu, unsigned int pll) { > + int index, hw_cpu = get_hard_smp_processor_id(cpu); > + int shift; > + u32 corefreq, val, mask = 0; > + unsigned int cur_pll = get_pll(hw_cpu); > + unsigned long flags; > + int ret = 0; > + > + if (pll == cur_pll) > + return 0; > + > + shift = hw_cpu * CORE_RATIO_STRIDE + CORE_RATIO_SHIFT; > + val = (pll & CORE_RATIO_MASK) << shift; > + > + corefreq = sysfreq * pll / 2; > + /* > + * Set the COREx_SPD bit if the requested core frequency > + * is larger than the threshold frequency. > + */ > + if (corefreq > FREQ_500MHz) > + val |= PMJCR_CORE0_SPD << hw_cpu; > + > + mask = (CORE_RATIO_MASK << shift) | (PMJCR_CORE0_SPD << hw_cpu); > + clrsetbits_be32(guts + PMJCR, mask, val); > + > + /* readback to sync write */ > + in_be32(guts + PMJCR); > + > + cpu_hotplug_disable_before_freeze(); > + /* > + * A Jog request can not be asserted when any core is in a low > + * power state on P1022. Before executing a jog request, any > + * core which is in a low power state must be waked by a > + * interrupt, and keep waking up until the sequence is > + * finished. > + */ > + for_each_present_cpu(index) { > + if (!cpu_online(index)) { > + cpu_hotplug_enable_after_thaw(); > + pr_err("%s: error, core%d is down.\n", __func__, index); > + return -1; > + } > + } > + > + atomic_set(&in_jog_process, 0); > + smp_call_function(spin_while_jogging, NULL, 0); > + > + local_irq_save(flags); > + > + /* Wait for the other core to wake. */ > + if (!spin_event_timeout(atomic_read(&in_jog_process) == 1, 1000, > 100)) { > + pr_err("%s: timeout, the other core is not at running > state.\n", > + __func__); > + ret = -1; > + goto err; > + } > + > + out_be32(guts + POWMGTCSR, POWMGTCSR_JOG | POWMGTCSR_INT_MASK); > + > + if (!spin_event_timeout( > + (in_be32(guts + POWMGTCSR) & POWMGTCSR_JOG) == 0, 1000, 100)) > { > + pr_err("%s: timeout, fail to switch the core frequency.\n", > + __func__); > + ret = -1; > + goto err; > + } > + > + clrbits32(guts + POWMGTCSR, POWMGTCSR_INT_MASK); > + in_be32(guts + POWMGTCSR); > + > + atomic_set(&in_jog_process, 0); > +err: > + local_irq_restore(flags); > + cpu_hotplug_enable_after_thaw(); > + > + /* verify */ > + cur_pll = get_pll(hw_cpu); > + if (cur_pll != pll) { > + pr_err("%s: error, the current PLL of core %d is %d instead > of %d.\n", > + __func__, hw_cpu, cur_pll, pll); > + return -1; > + } > + > + return ret; > +} > + > +/* > + * cpufreq functions > + */ > +static int mpc85xx_cpufreq_cpu_init(struct cpufreq_policy *policy) { > + unsigned int i, cur_pll; > + int hw_cpu = get_hard_smp_processor_id(policy->cpu); > + > + if (!cpu_present(policy->cpu)) > + return -ENODEV; > + > + /* the latency of a transition, the unit is ns */ > + policy->cpuinfo.transition_latency = 2000; > + > + cur_pll = get_pll(hw_cpu); > + > + /* initialize frequency table */ > + pr_debug("core%d frequency table:\n", hw_cpu); > + for (i = 0; mpc85xx_freqs[i].frequency != CPUFREQ_TABLE_END; i++) { > + if (mpc85xx_freqs[i].index <= max_pll[hw_cpu]) { > + /* The frequency unit is kHz. */ > + mpc85xx_freqs[i].frequency = > + (sysfreq * mpc85xx_freqs[i].index / 2) / 1000; > + } else { > + mpc85xx_freqs[i].frequency = CPUFREQ_ENTRY_INVALID; > + } > + > + pr_debug("%d: %dkHz\n", i, mpc85xx_freqs[i].frequency); > + > + if (mpc85xx_freqs[i].index == cur_pll) > + policy->cur = mpc85xx_freqs[i].frequency; > + } > + pr_debug("current pll is at %d, and core freq is%d\n", > + cur_pll, policy->cur); > + > + cpufreq_frequency_table_get_attr(mpc85xx_freqs, policy->cpu); > + > + /* > + * This ensures that policy->cpuinfo_min > + * and policy->cpuinfo_max are set correctly. > + */ > + return cpufreq_frequency_table_cpuinfo(policy, mpc85xx_freqs); } > + > +static int mpc85xx_cpufreq_cpu_exit(struct cpufreq_policy *policy) { > + cpufreq_frequency_table_put_attr(policy->cpu); > + > + return 0; > +} > + > +static int mpc85xx_cpufreq_verify(struct cpufreq_policy *policy) { > + return cpufreq_frequency_table_verify(policy, mpc85xx_freqs); } > + > +static int mpc85xx_cpufreq_target(struct cpufreq_policy *policy, > + unsigned int target_freq, > + unsigned int relation) > +{ > + struct cpufreq_freqs freqs; > + unsigned int new; > + int ret = 0; > + > + if (!set_pll) > + return -ENODEV; > + > + cpufreq_frequency_table_target(policy, > + mpc85xx_freqs, > + target_freq, > + relation, > + &new); > + > + freqs.old = policy->cur; > + freqs.new = mpc85xx_freqs[new].frequency; > + freqs.cpu = policy->cpu; > + > + mutex_lock(&mpc85xx_switch_mutex); > + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); > + > + ret = set_pll(policy->cpu, mpc85xx_freqs[new].index); > + if (!ret) { > + pr_info("cpufreq: Setting core%d frequency to %d kHz and PLL > ratio to %d:2\n", > + policy->cpu, mpc85xx_freqs[new].frequency, > + mpc85xx_freqs[new].index); > + > + ppc_proc_freq = freqs.new * 1000ul; > + } > + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); > + mutex_unlock(&mpc85xx_switch_mutex); > + > + return ret; > +} > + > +static struct cpufreq_driver mpc85xx_cpufreq_driver = { > + .verify = mpc85xx_cpufreq_verify, > + .target = mpc85xx_cpufreq_target, > + .init = mpc85xx_cpufreq_cpu_init, > + .exit = mpc85xx_cpufreq_cpu_exit, > + .name = "mpc85xx-JOG", > + .owner = THIS_MODULE, > + .flags = CPUFREQ_CONST_LOOPS, > +}; > + > +static int mpc85xx_job_probe(struct platform_device *ofdev) { > + struct device_node *np = ofdev->dev.of_node; > + unsigned int svr; > + > + if (of_device_is_compatible(np, "fsl,mpc8536-guts")) { > + svr = mfspr(SPRN_SVR); > + if ((svr & 0x7fff) == 0x10) { > + pr_err("MPC8536 Rev 1.0 do not support JOG.\n"); > + return -ENODEV; > + } > + mpc85xx_freqs = mpc8536_freqs_table; > + set_pll = mpc8536_set_pll; > + } else if (of_device_is_compatible(np, "fsl,p1022-guts")) { > + mpc85xx_freqs = p1022_freqs_table; > + set_pll = p1022_set_pll; > + } else { > + return -ENODEV; > + } > + > + sysfreq = fsl_get_sys_freq(); > + > + guts = of_iomap(np, 0); > + if (!guts) > + return -ENODEV; > + > + max_pll[0] = get_pll(0); > + if (mpc85xx_freqs == p1022_freqs_table) > + max_pll[1] = get_pll(1); > + > + pr_info("Freescale MPC85xx CPU frequency switching(JOG) driver\n"); > + > + return cpufreq_register_driver(&mpc85xx_cpufreq_driver); > +} > + > +static int mpc85xx_jog_remove(struct platform_device *ofdev) { > + iounmap(guts); > + cpufreq_unregister_driver(&mpc85xx_cpufreq_driver); > + > + return 0; > +} > + > +static struct of_device_id mpc85xx_jog_ids[] = { > + { .compatible = "fsl,mpc8536-guts", }, > + { .compatible = "fsl,p1022-guts", }, > + {} > +}; > + > +static struct platform_driver mpc85xx_jog_driver = { > + .driver = { > + .name = "mpc85xx_cpufreq_jog", > + .owner = THIS_MODULE, > + .of_match_table = mpc85xx_jog_ids, > + }, > + .probe = mpc85xx_job_probe, > + .remove = mpc85xx_jog_remove, > +}; > + > +static int __init mpc85xx_jog_init(void) { > + return platform_driver_register(&mpc85xx_jog_driver); > +} > + > +static void __exit mpc85xx_jog_exit(void) { > + platform_driver_unregister(&mpc85xx_jog_driver); > +} > + > +module_init(mpc85xx_jog_init); > +module_exit(mpc85xx_jog_exit); > + > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("Dave Liu <daveliu@freescale.com>"); > diff --git a/include/linux/cpu.h b/include/linux/cpu.h index > ce7a074..df8f73d 100644 > --- a/include/linux/cpu.h > +++ b/include/linux/cpu.h > @@ -146,6 +146,8 @@ void notify_cpu_starting(unsigned int cpu); extern > void cpu_maps_update_begin(void); extern void cpu_maps_update_done(void); > > +extern void cpu_hotplug_disable_before_freeze(void); > +extern void cpu_hotplug_enable_after_thaw(void); > #else /* CONFIG_SMP */ > > #define cpu_notifier(fn, pri) do { (void)(fn); } while (0) > @@ -167,6 +169,8 @@ static inline void cpu_maps_update_done(void) { } > > +static inline void cpu_hotplug_disable_before_freeze(void) {} > +static inline void cpu_hotplug_enable_after_thaw(void) {} > #endif /* CONFIG_SMP */ > extern struct bus_type cpu_subsys; > > diff --git a/kernel/cpu.c b/kernel/cpu.c index b5e4ab2..6fdc6df 100644 > --- a/kernel/cpu.c > +++ b/kernel/cpu.c > @@ -454,6 +454,36 @@ out: > } > EXPORT_SYMBOL_GPL(cpu_up); > > +/* > + * Prevent regular CPU hotplug from racing with the freezer, by > +disabling CPU > + * hotplug when tasks are about to be frozen. Also, don't allow the > +freezer > + * to continue until any currently running CPU hotplug operation gets > + * completed. > + * To modify the 'cpu_hotplug_disabled' flag, we need to acquire the > + * 'cpu_add_remove_lock'. And this same lock is also taken by the > +regular > + * CPU hotplug path and released only after it is complete. Thus, we > + * (and hence the freezer) will block here until any currently running > +CPU > + * hotplug operation gets completed. > + */ > +void cpu_hotplug_disable_before_freeze(void) > +{ > + cpu_maps_update_begin(); > + cpu_hotplug_disabled = 1; > + cpu_maps_update_done(); > +} > + > + > +/* > + * When tasks have been thawed, re-enable regular CPU hotplug (which > +had been > + * disabled while beginning to freeze tasks). > + */ > +void cpu_hotplug_enable_after_thaw(void) > +{ > + cpu_maps_update_begin(); > + cpu_hotplug_disabled = 0; > + cpu_maps_update_done(); > +} > + > #ifdef CONFIG_PM_SLEEP_SMP > static cpumask_var_t frozen_cpus; > > @@ -541,36 +571,6 @@ static int __init alloc_frozen_cpus(void) > core_initcall(alloc_frozen_cpus); > > /* > - * Prevent regular CPU hotplug from racing with the freezer, by > disabling CPU > - * hotplug when tasks are about to be frozen. Also, don't allow the > freezer > - * to continue until any currently running CPU hotplug operation gets > - * completed. > - * To modify the 'cpu_hotplug_disabled' flag, we need to acquire the > - * 'cpu_add_remove_lock'. And this same lock is also taken by the > regular > - * CPU hotplug path and released only after it is complete. Thus, we > - * (and hence the freezer) will block here until any currently running > CPU > - * hotplug operation gets completed. > - */ > -void cpu_hotplug_disable_before_freeze(void) > -{ > - cpu_maps_update_begin(); > - cpu_hotplug_disabled = 1; > - cpu_maps_update_done(); > -} > - > - > -/* > - * When tasks have been thawed, re-enable regular CPU hotplug (which had > been > - * disabled while beginning to freeze tasks). > - */ > -void cpu_hotplug_enable_after_thaw(void) > -{ > - cpu_maps_update_begin(); > - cpu_hotplug_disabled = 0; > - cpu_maps_update_done(); > -} > - > -/* > * When callbacks for CPU hotplug notifications are being executed, we > must > * ensure that the state of the system with respect to the tasks being > frozen > * or not, as reported by the notification, remains unchanged > *throughout the > -- > 1.7.3 > > > _______________________________________________ > Linuxppc-dev mailing list > Linuxppc-dev@lists.ozlabs.org > https://lists.ozlabs.org/listinfo/linuxppc-dev
On Sun, Apr 07, 2013 at 10:30:41AM +0800, Tang Yuantian-B29983 wrote: > Also send this patch to cpufreq@vger.kernel.org and linux-pm@vger.kernel.org > And better to rebase it on git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm.git > > Thanks, > Yuantian OK. Thanks. -Chenhui > > > -----Original Message----- > > From: Linuxppc-dev [mailto:linuxppc-dev- > > bounces+b29983=freescale.com@lists.ozlabs.org] On Behalf Of Zhao Chenhui > > Sent: 2013年4月3日 21:09 > > To: linuxppc-dev@lists.ozlabs.org > > Subject: [PATCH 08/17] powerpc/85xx: add support to JOG feature using > > cpufreq interface > > > > From: chenhui zhao <chenhui.zhao@freescale.com> > > > > Some MPC85xx SoCs like MPC8536 and P1022 have a JOG feature, which > > provides a dynamic mechanism to lower or raise the CPU core clock at > > runtime. > > > > This patch adds the support to change CPU frequency using the standard > > cpufreq interface. The ratio CORE to CCB can be 1:1(except MPC8536), 3:2, > > 2:1, 5:2, 3:1, 7:2 and 4:1. > > > > Two CPU cores on P1022 must not in the low power state during the > > frequency transition. The driver uses a flag to meet the requirement. > > > > The jog mode frequency transition process on the MPC8536 is similar to > > the deep sleep process. The driver need save the CPU state and restore it > > after CPU warm reset. > > > > Note: > > * The I/O peripherals such as PCIe and eTSEC may lose packets during > > the jog mode frequency transition. > > * The driver doesn't support MPC8536 Rev 1.0 due to a JOG erratum. > > Subsequent revisions of MPC8536 have corrected the erratum. > > > > Signed-off-by: Dave Liu <daveliu@freescale.com> > > Signed-off-by: Li Yang <leoli@freescale.com> > > Signed-off-by: Jerry Huang <Chang-Ming.Huang@freescale.com> > > Signed-off-by: Zhao Chenhui <chenhui.zhao@freescale.com>
diff --git a/arch/powerpc/platforms/85xx/Makefile b/arch/powerpc/platforms/85xx/Makefile index 2f4713f..4946be7 100644 --- a/arch/powerpc/platforms/85xx/Makefile +++ b/arch/powerpc/platforms/85xx/Makefile @@ -3,6 +3,7 @@ # obj-$(CONFIG_SMP) += smp.o obj-$(CONFIG_FSL_PMC) += sleep.o +obj-$(CONFIG_MPC85xx_CPUFREQ) += sleep.o obj-y += common.o diff --git a/arch/powerpc/sysdev/fsl_soc.h b/arch/powerpc/sysdev/fsl_soc.h index 29a87ee..b7d5ef7 100644 --- a/arch/powerpc/sysdev/fsl_soc.h +++ b/arch/powerpc/sysdev/fsl_soc.h @@ -62,5 +62,10 @@ void fsl_hv_halt(void); * code can be compatible with both 32-bit & 36-bit. */ extern void mpc85xx_enter_deep_sleep(u64 ccsrbar, u32 powmgtreq); + +static inline void mpc85xx_enter_jog(u64 ccsrbar, u32 powmgtreq) +{ + mpc85xx_enter_deep_sleep(ccsrbar, powmgtreq); +} #endif #endif diff --git a/drivers/cpufreq/Kconfig.powerpc b/drivers/cpufreq/Kconfig.powerpc index e76992f..c47a662 100644 --- a/drivers/cpufreq/Kconfig.powerpc +++ b/drivers/cpufreq/Kconfig.powerpc @@ -5,3 +5,13 @@ config CPU_FREQ_MAPLE help This adds support for frequency switching on Maple 970FX Evaluation Board and compatible boards (IBM JS2x blades). + +config MPC85xx_CPUFREQ + bool "Support for Freescale MPC85xx CPU freq" + depends on PPC_85xx && PPC32 && !PPC_E500MC + select CPU_FREQ_TABLE + help + This adds support for dynamic frequency switching on + Freescale MPC85xx by cpufreq interface. MPC8536 and P1022 + have a JOG feature, which provides a dynamic mechanism + to lower or raise the CPU core clock at runtime. diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 863fd18..628fa0e 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -61,3 +61,4 @@ obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q-cpufreq.o ################################################################################## # PowerPC platform drivers obj-$(CONFIG_CPU_FREQ_MAPLE) += maple-cpufreq.o +obj-$(CONFIG_MPC85xx_CPUFREQ) += cpufreq-jog.o diff --git a/drivers/cpufreq/cpufreq-jog.c b/drivers/cpufreq/cpufreq-jog.c new file mode 100644 index 0000000..5656d48 --- /dev/null +++ b/drivers/cpufreq/cpufreq-jog.c @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2008-2012 Freescale Semiconductor, Inc. + * Author: Dave Liu <daveliu@freescale.com> + * Modifier: Chenhui Zhao <chenhui.zhao@freescale.com> + * + * The cpufreq driver is for Freescale 85xx processor, + * based on arch/powerpc/platforms/cell/cbe_cpufreq.c + * (C) Copyright IBM Deutschland Entwicklung GmbH 2005-2007 + * Christian Krafft <krafft@de.ibm.com> + * + * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/cpufreq.h> +#include <linux/of_platform.h> +#include <linux/suspend.h> +#include <linux/cpu.h> +#include <linux/io.h> +#include <linux/time.h> +#include <linux/smp.h> + +#include <asm/prom.h> +#include <asm/reg.h> +#include <asm/machdep.h> + +#include <sysdev/fsl_soc.h> + +static DEFINE_MUTEX(mpc85xx_switch_mutex); +static void __iomem *guts; + +static u32 sysfreq; +static unsigned int max_pll[2]; +static atomic_t in_jog_process; +static struct cpufreq_frequency_table *mpc85xx_freqs; +static int (*set_pll)(unsigned int cpu, unsigned int pll); + +static struct cpufreq_frequency_table mpc8536_freqs_table[] = { + {3, 0}, + {4, 0}, + {5, 0}, + {6, 0}, + {7, 0}, + {8, 0}, + {0, CPUFREQ_TABLE_END}, +}; + +static struct cpufreq_frequency_table p1022_freqs_table[] = { + {2, 0}, + {3, 0}, + {4, 0}, + {5, 0}, + {6, 0}, + {7, 0}, + {8, 0}, + {0, CPUFREQ_TABLE_END}, +}; + +#define FREQ_500MHz 500000000 +#define FREQ_800MHz 800000000 + +#define CORE_RATIO_STRIDE 8 +#define CORE_RATIO_MASK 0x3f +#define CORE_RATIO_SHIFT 16 + +#define PORPLLSR 0x0 /* Power-On Reset PLL ratio status register */ + +#define PMJCR 0x7c /* Power Management Jog Control Register */ +#define PMJCR_CORE0_SPD 0x00001000 +#define PMJCR_CORE_SPD 0x00002000 + +#define POWMGTCSR 0x80 /* Power management control and status register */ +#define POWMGTCSR_JOG 0x00200000 +#define POWMGTCSR_INT_MASK 0x00000f00 + +static void spin_while_jogging(void *dummy) +{ + unsigned long flags; + + local_irq_save(flags); + + atomic_inc(&in_jog_process); + + while (atomic_read(&in_jog_process) != 0) + barrier(); + + local_irq_restore(flags); +} + +static int get_pll(int hw_cpu) +{ + int shift; + u32 val = in_be32(guts + PORPLLSR); + + shift = hw_cpu * CORE_RATIO_STRIDE + CORE_RATIO_SHIFT; + + return (val >> shift) & CORE_RATIO_MASK; +} + +static int mpc8536_set_pll(unsigned int cpu, unsigned int pll) +{ + u32 corefreq, val, mask; + unsigned int cur_pll = get_pll(0); + unsigned long flags; + + if (pll == cur_pll) + return 0; + + val = (pll & CORE_RATIO_MASK) << CORE_RATIO_SHIFT; + + corefreq = sysfreq * pll / 2; + /* + * Set the COREx_SPD bit if the requested core frequency + * is larger than the threshold frequency. + */ + if (corefreq > FREQ_800MHz) + val |= PMJCR_CORE_SPD; + + mask = (CORE_RATIO_MASK << CORE_RATIO_SHIFT) | PMJCR_CORE_SPD; + clrsetbits_be32(guts + PMJCR, mask, val); + + /* readback to sync write */ + in_be32(guts + PMJCR); + + local_irq_save(flags); + mpc85xx_enter_jog(get_immrbase(), POWMGTCSR_JOG); + local_irq_restore(flags); + + /* verify */ + cur_pll = get_pll(0); + if (cur_pll != pll) { + pr_err("%s: error. The current PLL of core 0 is %d instead of %d.\n", + __func__, cur_pll, pll); + return -1; + } + + return 0; +} + +static int p1022_set_pll(unsigned int cpu, unsigned int pll) +{ + int index, hw_cpu = get_hard_smp_processor_id(cpu); + int shift; + u32 corefreq, val, mask = 0; + unsigned int cur_pll = get_pll(hw_cpu); + unsigned long flags; + int ret = 0; + + if (pll == cur_pll) + return 0; + + shift = hw_cpu * CORE_RATIO_STRIDE + CORE_RATIO_SHIFT; + val = (pll & CORE_RATIO_MASK) << shift; + + corefreq = sysfreq * pll / 2; + /* + * Set the COREx_SPD bit if the requested core frequency + * is larger than the threshold frequency. + */ + if (corefreq > FREQ_500MHz) + val |= PMJCR_CORE0_SPD << hw_cpu; + + mask = (CORE_RATIO_MASK << shift) | (PMJCR_CORE0_SPD << hw_cpu); + clrsetbits_be32(guts + PMJCR, mask, val); + + /* readback to sync write */ + in_be32(guts + PMJCR); + + cpu_hotplug_disable_before_freeze(); + /* + * A Jog request can not be asserted when any core is in a low + * power state on P1022. Before executing a jog request, any + * core which is in a low power state must be waked by a + * interrupt, and keep waking up until the sequence is + * finished. + */ + for_each_present_cpu(index) { + if (!cpu_online(index)) { + cpu_hotplug_enable_after_thaw(); + pr_err("%s: error, core%d is down.\n", __func__, index); + return -1; + } + } + + atomic_set(&in_jog_process, 0); + smp_call_function(spin_while_jogging, NULL, 0); + + local_irq_save(flags); + + /* Wait for the other core to wake. */ + if (!spin_event_timeout(atomic_read(&in_jog_process) == 1, 1000, 100)) { + pr_err("%s: timeout, the other core is not at running state.\n", + __func__); + ret = -1; + goto err; + } + + out_be32(guts + POWMGTCSR, POWMGTCSR_JOG | POWMGTCSR_INT_MASK); + + if (!spin_event_timeout( + (in_be32(guts + POWMGTCSR) & POWMGTCSR_JOG) == 0, 1000, 100)) { + pr_err("%s: timeout, fail to switch the core frequency.\n", + __func__); + ret = -1; + goto err; + } + + clrbits32(guts + POWMGTCSR, POWMGTCSR_INT_MASK); + in_be32(guts + POWMGTCSR); + + atomic_set(&in_jog_process, 0); +err: + local_irq_restore(flags); + cpu_hotplug_enable_after_thaw(); + + /* verify */ + cur_pll = get_pll(hw_cpu); + if (cur_pll != pll) { + pr_err("%s: error, the current PLL of core %d is %d instead of %d.\n", + __func__, hw_cpu, cur_pll, pll); + return -1; + } + + return ret; +} + +/* + * cpufreq functions + */ +static int mpc85xx_cpufreq_cpu_init(struct cpufreq_policy *policy) +{ + unsigned int i, cur_pll; + int hw_cpu = get_hard_smp_processor_id(policy->cpu); + + if (!cpu_present(policy->cpu)) + return -ENODEV; + + /* the latency of a transition, the unit is ns */ + policy->cpuinfo.transition_latency = 2000; + + cur_pll = get_pll(hw_cpu); + + /* initialize frequency table */ + pr_debug("core%d frequency table:\n", hw_cpu); + for (i = 0; mpc85xx_freqs[i].frequency != CPUFREQ_TABLE_END; i++) { + if (mpc85xx_freqs[i].index <= max_pll[hw_cpu]) { + /* The frequency unit is kHz. */ + mpc85xx_freqs[i].frequency = + (sysfreq * mpc85xx_freqs[i].index / 2) / 1000; + } else { + mpc85xx_freqs[i].frequency = CPUFREQ_ENTRY_INVALID; + } + + pr_debug("%d: %dkHz\n", i, mpc85xx_freqs[i].frequency); + + if (mpc85xx_freqs[i].index == cur_pll) + policy->cur = mpc85xx_freqs[i].frequency; + } + pr_debug("current pll is at %d, and core freq is%d\n", + cur_pll, policy->cur); + + cpufreq_frequency_table_get_attr(mpc85xx_freqs, policy->cpu); + + /* + * This ensures that policy->cpuinfo_min + * and policy->cpuinfo_max are set correctly. + */ + return cpufreq_frequency_table_cpuinfo(policy, mpc85xx_freqs); +} + +static int mpc85xx_cpufreq_cpu_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + + return 0; +} + +static int mpc85xx_cpufreq_verify(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, mpc85xx_freqs); +} + +static int mpc85xx_cpufreq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + struct cpufreq_freqs freqs; + unsigned int new; + int ret = 0; + + if (!set_pll) + return -ENODEV; + + cpufreq_frequency_table_target(policy, + mpc85xx_freqs, + target_freq, + relation, + &new); + + freqs.old = policy->cur; + freqs.new = mpc85xx_freqs[new].frequency; + freqs.cpu = policy->cpu; + + mutex_lock(&mpc85xx_switch_mutex); + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + ret = set_pll(policy->cpu, mpc85xx_freqs[new].index); + if (!ret) { + pr_info("cpufreq: Setting core%d frequency to %d kHz and PLL ratio to %d:2\n", + policy->cpu, mpc85xx_freqs[new].frequency, + mpc85xx_freqs[new].index); + + ppc_proc_freq = freqs.new * 1000ul; + } + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + mutex_unlock(&mpc85xx_switch_mutex); + + return ret; +} + +static struct cpufreq_driver mpc85xx_cpufreq_driver = { + .verify = mpc85xx_cpufreq_verify, + .target = mpc85xx_cpufreq_target, + .init = mpc85xx_cpufreq_cpu_init, + .exit = mpc85xx_cpufreq_cpu_exit, + .name = "mpc85xx-JOG", + .owner = THIS_MODULE, + .flags = CPUFREQ_CONST_LOOPS, +}; + +static int mpc85xx_job_probe(struct platform_device *ofdev) +{ + struct device_node *np = ofdev->dev.of_node; + unsigned int svr; + + if (of_device_is_compatible(np, "fsl,mpc8536-guts")) { + svr = mfspr(SPRN_SVR); + if ((svr & 0x7fff) == 0x10) { + pr_err("MPC8536 Rev 1.0 do not support JOG.\n"); + return -ENODEV; + } + mpc85xx_freqs = mpc8536_freqs_table; + set_pll = mpc8536_set_pll; + } else if (of_device_is_compatible(np, "fsl,p1022-guts")) { + mpc85xx_freqs = p1022_freqs_table; + set_pll = p1022_set_pll; + } else { + return -ENODEV; + } + + sysfreq = fsl_get_sys_freq(); + + guts = of_iomap(np, 0); + if (!guts) + return -ENODEV; + + max_pll[0] = get_pll(0); + if (mpc85xx_freqs == p1022_freqs_table) + max_pll[1] = get_pll(1); + + pr_info("Freescale MPC85xx CPU frequency switching(JOG) driver\n"); + + return cpufreq_register_driver(&mpc85xx_cpufreq_driver); +} + +static int mpc85xx_jog_remove(struct platform_device *ofdev) +{ + iounmap(guts); + cpufreq_unregister_driver(&mpc85xx_cpufreq_driver); + + return 0; +} + +static struct of_device_id mpc85xx_jog_ids[] = { + { .compatible = "fsl,mpc8536-guts", }, + { .compatible = "fsl,p1022-guts", }, + {} +}; + +static struct platform_driver mpc85xx_jog_driver = { + .driver = { + .name = "mpc85xx_cpufreq_jog", + .owner = THIS_MODULE, + .of_match_table = mpc85xx_jog_ids, + }, + .probe = mpc85xx_job_probe, + .remove = mpc85xx_jog_remove, +}; + +static int __init mpc85xx_jog_init(void) +{ + return platform_driver_register(&mpc85xx_jog_driver); +} + +static void __exit mpc85xx_jog_exit(void) +{ + platform_driver_unregister(&mpc85xx_jog_driver); +} + +module_init(mpc85xx_jog_init); +module_exit(mpc85xx_jog_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Dave Liu <daveliu@freescale.com>"); diff --git a/include/linux/cpu.h b/include/linux/cpu.h index ce7a074..df8f73d 100644 --- a/include/linux/cpu.h +++ b/include/linux/cpu.h @@ -146,6 +146,8 @@ void notify_cpu_starting(unsigned int cpu); extern void cpu_maps_update_begin(void); extern void cpu_maps_update_done(void); +extern void cpu_hotplug_disable_before_freeze(void); +extern void cpu_hotplug_enable_after_thaw(void); #else /* CONFIG_SMP */ #define cpu_notifier(fn, pri) do { (void)(fn); } while (0) @@ -167,6 +169,8 @@ static inline void cpu_maps_update_done(void) { } +static inline void cpu_hotplug_disable_before_freeze(void) {} +static inline void cpu_hotplug_enable_after_thaw(void) {} #endif /* CONFIG_SMP */ extern struct bus_type cpu_subsys; diff --git a/kernel/cpu.c b/kernel/cpu.c index b5e4ab2..6fdc6df 100644 --- a/kernel/cpu.c +++ b/kernel/cpu.c @@ -454,6 +454,36 @@ out: } EXPORT_SYMBOL_GPL(cpu_up); +/* + * Prevent regular CPU hotplug from racing with the freezer, by disabling CPU + * hotplug when tasks are about to be frozen. Also, don't allow the freezer + * to continue until any currently running CPU hotplug operation gets + * completed. + * To modify the 'cpu_hotplug_disabled' flag, we need to acquire the + * 'cpu_add_remove_lock'. And this same lock is also taken by the regular + * CPU hotplug path and released only after it is complete. Thus, we + * (and hence the freezer) will block here until any currently running CPU + * hotplug operation gets completed. + */ +void cpu_hotplug_disable_before_freeze(void) +{ + cpu_maps_update_begin(); + cpu_hotplug_disabled = 1; + cpu_maps_update_done(); +} + + +/* + * When tasks have been thawed, re-enable regular CPU hotplug (which had been + * disabled while beginning to freeze tasks). + */ +void cpu_hotplug_enable_after_thaw(void) +{ + cpu_maps_update_begin(); + cpu_hotplug_disabled = 0; + cpu_maps_update_done(); +} + #ifdef CONFIG_PM_SLEEP_SMP static cpumask_var_t frozen_cpus; @@ -541,36 +571,6 @@ static int __init alloc_frozen_cpus(void) core_initcall(alloc_frozen_cpus); /* - * Prevent regular CPU hotplug from racing with the freezer, by disabling CPU - * hotplug when tasks are about to be frozen. Also, don't allow the freezer - * to continue until any currently running CPU hotplug operation gets - * completed. - * To modify the 'cpu_hotplug_disabled' flag, we need to acquire the - * 'cpu_add_remove_lock'. And this same lock is also taken by the regular - * CPU hotplug path and released only after it is complete. Thus, we - * (and hence the freezer) will block here until any currently running CPU - * hotplug operation gets completed. - */ -void cpu_hotplug_disable_before_freeze(void) -{ - cpu_maps_update_begin(); - cpu_hotplug_disabled = 1; - cpu_maps_update_done(); -} - - -/* - * When tasks have been thawed, re-enable regular CPU hotplug (which had been - * disabled while beginning to freeze tasks). - */ -void cpu_hotplug_enable_after_thaw(void) -{ - cpu_maps_update_begin(); - cpu_hotplug_disabled = 0; - cpu_maps_update_done(); -} - -/* * When callbacks for CPU hotplug notifications are being executed, we must * ensure that the state of the system with respect to the tasks being frozen * or not, as reported by the notification, remains unchanged *throughout the