|
@@ -156,3 +156,175 @@ static inline void s3c_cpufreq_updateclk(struct clk *clk,
|
|
|
clk_set_rate(clk, freq);
|
|
|
}
|
|
|
|
|
|
+static int s3c_cpufreq_settarget(struct cpufreq_policy *policy,
|
|
|
+ unsigned int target_freq,
|
|
|
+ struct cpufreq_frequency_table *pll)
|
|
|
+{
|
|
|
+ struct s3c_cpufreq_freqs freqs;
|
|
|
+ struct s3c_cpufreq_config cpu_new;
|
|
|
+ unsigned long flags;
|
|
|
+
|
|
|
+ cpu_new = cpu_cur; /* copy new from current */
|
|
|
+
|
|
|
+ s3c_cpufreq_show("cur", &cpu_cur);
|
|
|
+
|
|
|
+ /* TODO - check for DMA currently outstanding */
|
|
|
+
|
|
|
+ cpu_new.pll = pll ? *pll : cpu_cur.pll;
|
|
|
+
|
|
|
+ if (pll)
|
|
|
+ freqs.pll_changing = 1;
|
|
|
+
|
|
|
+ /* update our frequencies */
|
|
|
+
|
|
|
+ cpu_new.freq.armclk = target_freq;
|
|
|
+ cpu_new.freq.fclk = cpu_new.pll.frequency;
|
|
|
+
|
|
|
+ if (s3c_cpufreq_calcdivs(&cpu_new) < 0) {
|
|
|
+ printk(KERN_ERR "no divisors for %d\n", target_freq);
|
|
|
+ goto err_notpossible;
|
|
|
+ }
|
|
|
+
|
|
|
+ s3c_freq_dbg("%s: got divs\n", __func__);
|
|
|
+
|
|
|
+ s3c_cpufreq_calc(&cpu_new);
|
|
|
+
|
|
|
+ s3c_freq_dbg("%s: calculated frequencies for new\n", __func__);
|
|
|
+
|
|
|
+ if (cpu_new.freq.hclk != cpu_cur.freq.hclk) {
|
|
|
+ if (s3c_cpufreq_calcio(&cpu_new) < 0) {
|
|
|
+ printk(KERN_ERR "%s: no IO timings\n", __func__);
|
|
|
+ goto err_notpossible;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ s3c_cpufreq_show("new", &cpu_new);
|
|
|
+
|
|
|
+ /* setup our cpufreq parameters */
|
|
|
+
|
|
|
+ freqs.old = cpu_cur.freq;
|
|
|
+ freqs.new = cpu_new.freq;
|
|
|
+
|
|
|
+ freqs.freqs.cpu = 0;
|
|
|
+ freqs.freqs.old = cpu_cur.freq.armclk / 1000;
|
|
|
+ freqs.freqs.new = cpu_new.freq.armclk / 1000;
|
|
|
+
|
|
|
+ /* update f/h/p clock settings before we issue the change
|
|
|
+ * notification, so that drivers do not need to do anything
|
|
|
+ * special if they want to recalculate on CPUFREQ_PRECHANGE. */
|
|
|
+
|
|
|
+ s3c_cpufreq_updateclk(_clk_mpll, cpu_new.pll.frequency);
|
|
|
+ s3c_cpufreq_updateclk(clk_fclk, cpu_new.freq.fclk);
|
|
|
+ s3c_cpufreq_updateclk(clk_hclk, cpu_new.freq.hclk);
|
|
|
+ s3c_cpufreq_updateclk(clk_pclk, cpu_new.freq.pclk);
|
|
|
+
|
|
|
+ /* start the frequency change */
|
|
|
+
|
|
|
+ if (policy)
|
|
|
+ cpufreq_notify_transition(&freqs.freqs, CPUFREQ_PRECHANGE);
|
|
|
+
|
|
|
+ /* If hclk is staying the same, then we do not need to
|
|
|
+ * re-write the IO or the refresh timings whilst we are changing
|
|
|
+ * speed. */
|
|
|
+
|
|
|
+ local_irq_save(flags);
|
|
|
+
|
|
|
+ /* is our memory clock slowing down? */
|
|
|
+ if (cpu_new.freq.hclk < cpu_cur.freq.hclk) {
|
|
|
+ s3c_cpufreq_setrefresh(&cpu_new);
|
|
|
+ s3c_cpufreq_setio(&cpu_new);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (cpu_new.freq.fclk == cpu_cur.freq.fclk) {
|
|
|
+ /* not changing PLL, just set the divisors */
|
|
|
+
|
|
|
+ s3c_cpufreq_setdivs(&cpu_new);
|
|
|
+ } else {
|
|
|
+ if (cpu_new.freq.fclk < cpu_cur.freq.fclk) {
|
|
|
+ /* slow the cpu down, then set divisors */
|
|
|
+
|
|
|
+ s3c_cpufreq_setfvco(&cpu_new);
|
|
|
+ s3c_cpufreq_setdivs(&cpu_new);
|
|
|
+ } else {
|
|
|
+ /* set the divisors, then speed up */
|
|
|
+
|
|
|
+ s3c_cpufreq_setdivs(&cpu_new);
|
|
|
+ s3c_cpufreq_setfvco(&cpu_new);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* did our memory clock speed up */
|
|
|
+ if (cpu_new.freq.hclk > cpu_cur.freq.hclk) {
|
|
|
+ s3c_cpufreq_setrefresh(&cpu_new);
|
|
|
+ s3c_cpufreq_setio(&cpu_new);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* update our current settings */
|
|
|
+ cpu_cur = cpu_new;
|
|
|
+
|
|
|
+ local_irq_restore(flags);
|
|
|
+
|
|
|
+ /* notify everyone we've done this */
|
|
|
+ if (policy)
|
|
|
+ cpufreq_notify_transition(&freqs.freqs, CPUFREQ_POSTCHANGE);
|
|
|
+
|
|
|
+ s3c_freq_dbg("%s: finished\n", __func__);
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ err_notpossible:
|
|
|
+ printk(KERN_ERR "no compatible settings for %d\n", target_freq);
|
|
|
+ return -EINVAL;
|
|
|
+}
|
|
|
+
|
|
|
+/* s3c_cpufreq_target
|
|
|
+ *
|
|
|
+ * called by the cpufreq core to adjust the frequency that the CPU
|
|
|
+ * is currently running at.
|
|
|
+ */
|
|
|
+
|
|
|
+static int s3c_cpufreq_target(struct cpufreq_policy *policy,
|
|
|
+ unsigned int target_freq,
|
|
|
+ unsigned int relation)
|
|
|
+{
|
|
|
+ struct cpufreq_frequency_table *pll;
|
|
|
+ unsigned int index;
|
|
|
+
|
|
|
+ /* avoid repeated calls which cause a needless amout of duplicated
|
|
|
+ * logging output (and CPU time as the calculation process is
|
|
|
+ * done) */
|
|
|
+ if (target_freq == last_target)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ last_target = target_freq;
|
|
|
+
|
|
|
+ s3c_freq_dbg("%s: policy %p, target %u, relation %u\n",
|
|
|
+ __func__, policy, target_freq, relation);
|
|
|
+
|
|
|
+ if (ftab) {
|
|
|
+ if (cpufreq_frequency_table_target(policy, ftab,
|
|
|
+ target_freq, relation,
|
|
|
+ &index)) {
|
|
|
+ s3c_freq_dbg("%s: table failed\n", __func__);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ s3c_freq_dbg("%s: adjust %d to entry %d (%u)\n", __func__,
|
|
|
+ target_freq, index, ftab[index].frequency);
|
|
|
+ target_freq = ftab[index].frequency;
|
|
|
+ }
|
|
|
+
|
|
|
+ target_freq *= 1000; /* convert target to Hz */
|
|
|
+
|
|
|
+ /* find the settings for our new frequency */
|
|
|
+
|
|
|
+ if (!pll_reg || cpu_cur.lock_pll) {
|
|
|
+ /* either we've not got any PLL values, or we've locked
|
|
|
+ * to the current one. */
|
|
|
+ pll = NULL;
|
|
|
+ } else {
|
|
|
+ struct cpufreq_policy tmp_policy;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* we keep the cpu pll table in Hz, to ensure we get an
|
|
|
+ * accurate value for the PLL output. */
|
|
|
+
|