On Friday, April 19, 2013 07:00:57 PM Zhao Chenhui wrote: > ----- Forwarded message from Zhao Chenhui <chenhui.zhao@xxxxxxxxxxxxx> ----- > > Date: Fri, 19 Apr 2013 18:47:39 +0800 > From: Zhao Chenhui <chenhui.zhao@xxxxxxxxxxxxx> > To: linuxppc-dev@xxxxxxxxxxxxxxxx > CC: linux-kernel@xxxxxxxxxxxxxxx > Subject: [linuxppc-release] [PATCH v2 06/15] powerpc/85xx: add support to JOG feature using cpufreq interface > X-Mailer: git-send-email 1.7.3 > > From: chenhui zhao <chenhui.zhao@xxxxxxxxxxxxx> > > Some 85xx silicons 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 atomic counter 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@xxxxxxxxxxxxx> > Signed-off-by: Li Yang <leoli@xxxxxxxxxxxxx> > Signed-off-by: Jerry Huang <Chang-Ming.Huang@xxxxxxxxxxxxx> > Signed-off-by: Zhao Chenhui <chenhui.zhao@xxxxxxxxxxxxx> > CC: Scott Wood <scottwood@xxxxxxxxxxxxx> Well, I'd like someone from the PowerPC camp to comment on this before I take it. Thanks, Rafael > --- > arch/powerpc/platforms/85xx/Makefile | 1 + > drivers/cpufreq/Kconfig.powerpc | 10 + > drivers/cpufreq/Makefile | 1 + > drivers/cpufreq/mpc85xx-cpufreq.c | 390 ++++++++++++++++++++++++++++++++++ > 4 files changed, 402 insertions(+), 0 deletions(-) > create mode 100644 drivers/cpufreq/mpc85xx-cpufreq.c > > diff --git a/arch/powerpc/platforms/85xx/Makefile b/arch/powerpc/platforms/85xx/Makefile > index a35bab7..da53bde 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_CPU_FREQ_MPC85xx) += sleep.o > > obj-y += common.o > > diff --git a/drivers/cpufreq/Kconfig.powerpc b/drivers/cpufreq/Kconfig.powerpc > index e76992f..ba06a00 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 CPU_FREQ_MPC85xx > + 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..e7aecc5 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_CPU_FREQ_MPC85xx) += mpc85xx-cpufreq.o > diff --git a/drivers/cpufreq/mpc85xx-cpufreq.c b/drivers/cpufreq/mpc85xx-cpufreq.c > new file mode 100644 > index 0000000..f56c826 > --- /dev/null > +++ b/drivers/cpufreq/mpc85xx-cpufreq.c > @@ -0,0 +1,390 @@ > +/* > + * Copyright (C) 2008-2012 Freescale Semiconductor, Inc. > + * Author: Dave Liu <daveliu@xxxxxxxxxxxxx> > + * Modifier: Chenhui Zhao <chenhui.zhao@xxxxxxxxxxxxx> > + * > + * 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@xxxxxxxxxx> > + * > + * 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/time.h> > +#include <linux/io.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_deep_sleep(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 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); > + > + get_online_cpus(); > + /* > + * 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)) { > + put_online_cpus(); > + 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); > + put_online_cpus(); > + > + /* 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 struct of_device_id mpc85xx_jog_ids[] = { > + { .compatible = "fsl,mpc8536-guts", }, > + { .compatible = "fsl,p1022-guts", }, > + {} > +}; > + > +static int __init mpc85xx_jog_init(void) > +{ > + struct device_node *np; > + unsigned int svr; > + > + np = of_find_matching_node(NULL, mpc85xx_jog_ids); > + if (!np) > + return -ENODEV; > + > + guts = of_iomap(np, 0); > + if (!guts) { > + of_node_put(np); > + return -ENODEV; > + } > + > + sysfreq = fsl_get_sys_freq(); > + > + if (of_device_is_compatible(np, "fsl,mpc8536-guts")) { > + svr = mfspr(SPRN_SVR); > + if ((svr & 0x7fff) == 0x10) { > + pr_err("MPC8536 Rev 1.0 does not support cpufreq(JOG).\n"); > + of_node_put(np); > + return -ENODEV; > + } > + mpc85xx_freqs = mpc8536_freqs_table; > + set_pll = mpc8536_set_pll; > + max_pll[0] = get_pll(0); > + > + } else if (of_device_is_compatible(np, "fsl,p1022-guts")) { > + mpc85xx_freqs = p1022_freqs_table; > + set_pll = p1022_set_pll; > + max_pll[0] = get_pll(0); > + max_pll[1] = get_pll(1); > + } > + > + pr_info("Freescale MPC85xx cpufreq(JOG) driver\n"); > + > + of_node_put(np); > + return cpufreq_register_driver(&mpc85xx_cpufreq_driver); > +} > + > +device_initcall(mpc85xx_jog_init); > -- I speak only for myself. Rafael J. Wysocki, Intel Open Source Technology Center. -- To unsubscribe from this list: send the line "unsubscribe cpufreq" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html