#include <mach/hardware.h>
+/* OPP tolerance in percentage */
+#define OPP_TOLERANCE 4
+
#ifdef CONFIG_SMP
struct lpj_info {
unsigned long ref;
static char *mpu_clk_name;
static struct device *mpu_dev;
static struct regulator *mpu_reg;
+static unsigned long freq_max, volt_max;
static int omap_verify_speed(struct cpufreq_policy *policy)
{
/* scaling up? scale voltage before frequency */
if (mpu_reg && (freqs.new > freqs.old)) {
- r = regulator_set_voltage(mpu_reg, volt, volt);
+ r = regulator_set_voltage(mpu_reg, volt, volt_max);
if (r < 0) {
dev_warn(mpu_dev, "%s: unable to scale voltage up.\n",
__func__);
/* scaling down? scale voltage after frequency */
if (mpu_reg && (freqs.new < freqs.old)) {
- r = regulator_set_voltage(mpu_reg, volt, volt);
+ r = regulator_set_voltage(mpu_reg, volt, volt_max);
if (r < 0) {
dev_warn(mpu_dev, "%s: unable to scale voltage down.\n",
__func__);
opp_free_cpufreq_table(mpu_dev, &freq_table);
}
+/* force-update hack */
+static struct notifier_block omap_freq_nb;
+static struct cpufreq_policy *omap_freq_policy;
+
+static void check_max_freq(unsigned long freq)
+{
+ unsigned long volt;
+ struct opp *opp;
+
+ freq *= 1000;
+
+ if (freq <= freq_max)
+ return;
+
+ opp = opp_find_freq_ceil(mpu_dev, &freq);
+ if (IS_ERR(opp)) {
+ dev_err(mpu_dev, "%s: unable to find MPU OPP for %ld\n",
+ __func__, freq);
+ return;
+ }
+
+ volt = opp_get_voltage(opp);
+ volt += volt * OPP_TOLERANCE / 100;
+
+ if (volt > volt_max) {
+ volt_max = volt;
+ freq_max = freq;
+ }
+}
+
+static int freq_notifier_call(struct notifier_block *nb, unsigned long type,
+ void *devp)
+{
+ static DEFINE_SPINLOCK(lock);
+ struct cpufreq_frequency_table *new_freq_table, *old_freq_table;
+ unsigned long flags;
+ int ret;
+
+ ret = opp_init_cpufreq_table(mpu_dev, &new_freq_table);
+ if (ret) {
+ dev_err(mpu_dev, "%s: failed to create cpufreq_table: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ /* FIXME: use proper locks instead of these hacks */
+ spin_lock_irqsave(&lock, flags);
+ old_freq_table = freq_table;
+ freq_table = new_freq_table;
+ spin_unlock_irqrestore(&lock, flags);
+ msleep(1);
+ opp_free_cpufreq_table(mpu_dev, &old_freq_table);
+
+ if (omap_freq_policy == NULL) {
+ dev_err(mpu_dev, "%s: omap_freq_policy is NULL\n", __func__);
+ return -EINVAL;
+ }
+
+ cpufreq_frequency_table_get_attr(freq_table, omap_freq_policy->cpu);
+
+ ret = cpufreq_frequency_table_cpuinfo(omap_freq_policy, freq_table);
+ if (ret)
+ dev_err(mpu_dev, "%s: cpufreq_frequency_table_cpuinfo: %d\n",
+ __func__, ret);
+ omap_freq_policy->user_policy.min = omap_freq_policy->cpuinfo.min_freq;
+ omap_freq_policy->user_policy.max = omap_freq_policy->cpuinfo.max_freq;
+
+ check_max_freq(omap_freq_policy->cpuinfo.max_freq);
+
+ return ret;
+}
+
+static void freq_register_opp_notifier(struct device *dev,
+ struct cpufreq_policy *policy)
+{
+ struct srcu_notifier_head *nh = opp_get_notifier(dev);
+ int ret;
+
+ omap_freq_policy = policy;
+
+ if (IS_ERR(nh)) {
+ ret = PTR_ERR(nh);
+ goto out;
+ }
+ omap_freq_nb.notifier_call = freq_notifier_call;
+ ret = srcu_notifier_chain_register(nh, &omap_freq_nb);
+out:
+ if (ret != 0)
+ dev_err(mpu_dev, "%s: failed to register notifier: %d\n",
+ __func__, ret);
+}
+
+static void freq_unregister_opp_notifier(struct device *dev)
+{
+ struct srcu_notifier_head *nh = opp_get_notifier(dev);
+
+ if (IS_ERR(nh))
+ return;
+ srcu_notifier_chain_unregister(nh, &omap_freq_nb);
+}
+
static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy)
{
int result = 0;
policy->max = policy->cpuinfo.max_freq;
policy->cur = omap_getspeed(policy->cpu);
+ check_max_freq(policy->cpuinfo.max_freq);
+
/*
* On OMAP SMP configuartion, both processors share the voltage
* and clock. So both CPUs needs to be scaled together and hence
/* FIXME: what's the actual transition time? */
policy->cpuinfo.transition_latency = 300 * 1000;
+ freq_register_opp_notifier(mpu_dev, policy);
+
return 0;
fail_table:
static int omap_cpu_exit(struct cpufreq_policy *policy)
{
+ freq_unregister_opp_notifier(mpu_dev);
freq_table_free();
clk_put(mpu_clk);
return 0;