From: Benjamin Herrenschmidt I didn't get any feedback from the cpufreq folks (except Dominik who seems to be ok) about this patch and I need it urgently for some pmac sleep fixes that rely on it that I would like to see in 2.6.12 final. In order to properly fix some issues with cpufreq vs. sleep on PowerBooks, I had to add a suspend callback to the pmac_cpufreq driver. I must force a switch to full speed before sleep and I switch back to previous speed on resume. I also added a driver flag to disable the warnings in suspend/resume since it is expected in this case to have different speed (and I want it to fixup the jiffies properly). Here is the patch to the cpufreq core. If it's ok with everybody, please submit it asap to Andrew as my fixes depend on it. I would like those to get in 2.6.12. Signed-off-by: Benjamin Herrenschmidt Signed-off-by: Andrew Morton --- 25-akpm/drivers/cpufreq/cpufreq.c | 85 +++++++++++++++++++++++++++++++++++++- 25-akpm/include/linux/cpufreq.h | 5 +- 2 files changed, 87 insertions(+), 3 deletions(-) diff -puN drivers/cpufreq/cpufreq.c~add-suspend-method-to-cpufreq-core drivers/cpufreq/cpufreq.c --- 25/drivers/cpufreq/cpufreq.c~add-suspend-method-to-cpufreq-core Mon Apr 4 15:02:08 2005 +++ 25-akpm/drivers/cpufreq/cpufreq.c Mon Apr 4 15:02:08 2005 @@ -223,7 +223,7 @@ static inline void adjust_jiffies(unsign } if ((val == CPUFREQ_PRECHANGE && ci->old < ci->new) || (val == CPUFREQ_POSTCHANGE && ci->old > ci->new) || - (val == CPUFREQ_RESUMECHANGE)) { + (val == CPUFREQ_RESUMECHANGE || val == CPUFREQ_SUSPENDCHANGE)) { loops_per_jiffy = cpufreq_scale(l_p_j_ref, l_p_j_ref_freq, ci->new); dprintk("scaling loops_per_jiffy to %lu for frequency %u kHz\n", loops_per_jiffy, ci->new); } @@ -866,16 +866,96 @@ EXPORT_SYMBOL(cpufreq_get); /** + * cpufreq_suspend - let the low level driver prepare for suspend + */ + +static int cpufreq_suspend(struct sys_device * sysdev, u32 state) +{ + int cpu = sysdev->id; + unsigned int ret = 0; + unsigned int cur_freq = 0; + struct cpufreq_policy *cpu_policy; + + dprintk("resuming cpu %u\n", cpu); + + if (!cpu_online(cpu)) + return 0; + + /* we may be lax here as interrupts are off. Nonetheless + * we need to grab the correct cpu policy, as to check + * whether we really run on this CPU. + */ + + cpu_policy = cpufreq_cpu_get(cpu); + if (!cpu_policy) + return -EINVAL; + + /* only handle each CPU group once */ + if (unlikely(cpu_policy->cpu != cpu)) { + cpufreq_cpu_put(cpu_policy); + return 0; + } + + if (cpufreq_driver->suspend) { + ret = cpufreq_driver->suspend(cpu_policy, state); + if (ret) { + printk(KERN_ERR "cpufreq: suspend failed in ->suspend " + "step on CPU %u\n", cpu_policy->cpu); + cpufreq_cpu_put(cpu_policy); + return ret; + } + } + + + if (cpufreq_driver->flags & CPUFREQ_CONST_LOOPS) + goto out; + + if (cpufreq_driver->get) + cur_freq = cpufreq_driver->get(cpu_policy->cpu); + + if (!cur_freq || !cpu_policy->cur) { + printk(KERN_ERR "cpufreq: suspend failed to assert current " + "frequency is what timing core thinks it is.\n"); + goto out; + } + + if (unlikely(cur_freq != cpu_policy->cur)) { + struct cpufreq_freqs freqs; + + if (!(cpufreq_driver->flags & CPUFREQ_PM_NO_WARN)) + printk(KERN_DEBUG "Warning: CPU frequency is %u, " + "cpufreq assumed %u kHz.\n", + cur_freq, cpu_policy->cur); + + freqs.cpu = cpu; + freqs.old = cpu_policy->cur; + freqs.new = cur_freq; + + notifier_call_chain(&cpufreq_transition_notifier_list, + CPUFREQ_SUSPENDCHANGE, &freqs); + adjust_jiffies(CPUFREQ_SUSPENDCHANGE, &freqs); + + cpu_policy->cur = cur_freq; + } + + out: + cpufreq_cpu_put(cpu_policy); + return 0; +} + +/** * cpufreq_resume - restore proper CPU frequency handling after resume * * 1.) resume CPUfreq hardware support (cpufreq_driver->resume()) * 2.) if ->target and !CPUFREQ_CONST_LOOPS: verify we're in sync - * 3.) schedule call cpufreq_update_policy() ASAP as interrupts are restored. + * 3.) schedule call cpufreq_update_policy() ASAP as interrupts are + * restored. */ static int cpufreq_resume(struct sys_device * sysdev) { int cpu = sysdev->id; unsigned int ret = 0; + unsigned int cur_freq = 0; struct cpufreq_policy *cpu_policy; dprintk("resuming cpu %u\n", cpu); @@ -945,6 +1025,7 @@ out: static struct sysdev_driver cpufreq_sysdev_driver = { .add = cpufreq_add_dev, .remove = cpufreq_remove_dev, + .suspend = cpufreq_suspend, .resume = cpufreq_resume, }; diff -puN include/linux/cpufreq.h~add-suspend-method-to-cpufreq-core include/linux/cpufreq.h --- 25/include/linux/cpufreq.h~add-suspend-method-to-cpufreq-core Mon Apr 4 15:02:08 2005 +++ 25-akpm/include/linux/cpufreq.h Mon Apr 4 15:02:08 2005 @@ -103,6 +103,7 @@ struct cpufreq_policy { #define CPUFREQ_PRECHANGE (0) #define CPUFREQ_POSTCHANGE (1) #define CPUFREQ_RESUMECHANGE (8) +#define CPUFREQ_SUSPENDCHANGE (9) struct cpufreq_freqs { unsigned int cpu; /* cpu nr */ @@ -200,6 +201,7 @@ struct cpufreq_driver { /* optional */ int (*exit) (struct cpufreq_policy *policy); + int (*suspend) (struct cpufreq_policy *policy, u32 state); int (*resume) (struct cpufreq_policy *policy); struct freq_attr **attr; }; @@ -211,7 +213,8 @@ struct cpufreq_driver { #define CPUFREQ_CONST_LOOPS 0x02 /* loops_per_jiffy or other kernel * "constants" aren't affected by * frequency transitions */ - +#define CPUFREQ_PM_NO_WARN 0x04 /* don't warn on suspend/resume speed + * mismatches */ int cpufreq_register_driver(struct cpufreq_driver *driver_data); int cpufreq_unregister_driver(struct cpufreq_driver *driver_data); _