]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/sh/clk/core.c
sh: add clk_round_parent() to optimize parent clock rate
[mv-sheeva.git] / drivers / sh / clk / core.c
index 861144360d8930587baa27d2303d21523e2581de..b3840597ad6efddf2042eecd01d60ff1b7aca18c 100644 (file)
@@ -541,6 +541,81 @@ long clk_round_rate(struct clk *clk, unsigned long rate)
 }
 EXPORT_SYMBOL_GPL(clk_round_rate);
 
+long clk_round_parent(struct clk *clk, unsigned long target,
+                     unsigned long *best_freq, unsigned long *parent_freq,
+                     unsigned int div_min, unsigned int div_max)
+{
+       struct cpufreq_frequency_table *freq, *best = NULL;
+       unsigned long error = ULONG_MAX, freq_high, freq_low, div;
+       struct clk *parent = clk_get_parent(clk);
+
+       if (!parent) {
+               *parent_freq = 0;
+               *best_freq = clk_round_rate(clk, target);
+               return abs(target - *best_freq);
+       }
+
+       for (freq = parent->freq_table; freq->frequency != CPUFREQ_TABLE_END;
+            freq++) {
+               if (freq->frequency == CPUFREQ_ENTRY_INVALID)
+                       continue;
+
+               if (unlikely(freq->frequency / target <= div_min - 1)) {
+                       unsigned long freq_max = (freq->frequency + div_min / 2) / div_min;
+                       if (error > target - freq_max) {
+                               error = target - freq_max;
+                               best = freq;
+                               if (best_freq)
+                                       *best_freq = freq_max;
+                       }
+                       pr_debug("too low freq %lu, error %lu\n", freq->frequency, target - freq_max);
+                       if (!error)
+                               break;
+                       continue;
+               }
+
+               if (unlikely(freq->frequency / target >= div_max)) {
+                       unsigned long freq_min = (freq->frequency + div_max / 2) / div_max;
+                       if (error > freq_min - target) {
+                               error = freq_min - target;
+                               best = freq;
+                               if (best_freq)
+                                       *best_freq = freq_min;
+                       }
+                       pr_debug("too high freq %lu, error %lu\n", freq->frequency, freq_min - target);
+                       if (!error)
+                               break;
+                       continue;
+               }
+
+
+               div = freq->frequency / target;
+               freq_high = freq->frequency / div;
+               freq_low = freq->frequency / (div + 1);
+               if (freq_high - target < error) {
+                       error = freq_high - target;
+                       best = freq;
+                       if (best_freq)
+                               *best_freq = freq_high;
+               }
+               if (target - freq_low < error) {
+                       error = target - freq_low;
+                       best = freq;
+                       if (best_freq)
+                               *best_freq = freq_low;
+               }
+               pr_debug("%u / %lu = %lu, / %lu = %lu, best %lu, parent %u\n",
+                        freq->frequency, div, freq_high, div + 1, freq_low,
+                        *best_freq, best->frequency);
+               if (!error)
+                       break;
+       }
+       if (parent_freq)
+               *parent_freq = best->frequency;
+       return error;
+}
+EXPORT_SYMBOL_GPL(clk_round_parent);
+
 #ifdef CONFIG_PM
 static int clks_sysdev_suspend(struct sys_device *dev, pm_message_t state)
 {