omap_overclocking: add support for IVA (DSP) overclocking
[pandora-kernel.git] / drivers / misc / omap_overclocking.c
index 7551abd..96e08cd 100644 (file)
@@ -9,57 +9,74 @@
 #include <linux/opp.h>
 #include <linux/clk.h>
 #include <linux/uaccess.h>
+#include <linux/regulator/consumer.h>
 
 #include <plat/omap_device.h>
 
 #define PROC_DIR       "pandora"
 #define PROC_CPUMHZ    "pandora/cpu_mhz_max"
+#define PROC_DSPMHZ    "pandora/dsp_mhz_max"
 #define PROC_CPUOPP    "pandora/cpu_opp_max"
 #define PROC_SYSMHZ    "pandora/sys_mhz_max"
 
-/* FIXME: could use opp3xxx_data.c, but that's initdata.. */
-static const unsigned long nominal_freqs_35xx[] = {
+static struct device *mpu_dev;
+
+static struct device *iva_dev;
+static struct regulator *iva_reg;
+static struct clk *iva_clk;
+static DEFINE_MUTEX(iva_lock);
+static struct delayed_work iva_work;
+static int iva_mhz_max;
+static int iva_opp_min;
+static int iva_active;
+
+/* XXX: could use opp3xxx_data.c, but that's initdata.. */
+static const unsigned long nominal_f_mpu_35xx[] = {
        125000000, 250000000, 500000000, 550000000, 600000000,
 };
 
-static const unsigned long nominal_freqs_36xx[] = {
+static const unsigned long nominal_f_mpu_36xx[] = {
        300000000, 600000000, 800000000, 1000000000,
 };
 
-static const unsigned long *nominal_freqs;
+static const unsigned long nominal_f_iva_35xx[] = {
+       90000000,  180000000, 360000000, 400000000, 430000000,
+};
+
+static const unsigned long nominal_f_iva_36xx[] = {
+       260000000, 520000000, 660000000, 800000000,
+};
+
+static const unsigned long *nominal_freqs_mpu;
+static const unsigned long *nominal_freqs_iva;
+
+/* IVA voltages (MPU ones are managed by cpufreq) */
+static unsigned long iva_voltages[5];
 
 static int opp_max_avail, opp_max_now, opp_max_ceil;
 
-static int set_opp_max(int new_opp_max)
+static int set_mpu_opp_max(int new_opp_max)
 {
-       struct device *mpu_dev;
        int i, ret;
 
        if (new_opp_max == opp_max_now)
                return 0;
 
-       mpu_dev = omap_device_get_by_hwmod_name("mpu");
-       if (IS_ERR(mpu_dev)) {
-               pr_err("%s: mpu device not available (%ld)\n",
-                       __func__, PTR_ERR(mpu_dev));
-               return -ENODEV;
-       }
-
        for (i = 1; i < new_opp_max; i++) {
                ret = opp_enable_i(mpu_dev, i);
                if (ret != 0)
-                       dev_err(mpu_dev, "%s: opp_enable returned %d\n",
+                       dev_err(mpu_dev, "%s: mpu opp_enable returned %d\n",
                                __func__, ret);
        }
 
        for (i = new_opp_max; i < opp_max_avail; i++) {
                ret = opp_disable_i(mpu_dev, i);
                if (ret != 0)
-                       dev_err(mpu_dev, "%s: opp_disable returned %d\n",
+                       dev_err(mpu_dev, "%s: mpu opp_disable returned %d\n",
                                __func__, ret);
        }
 
-       dev_info(mpu_dev, "max OPP set to %d\n", new_opp_max);
+       dev_info(mpu_dev, "max MPU OPP set to %d\n", new_opp_max);
        opp_max_now = new_opp_max;
 
        return 0;
@@ -68,13 +85,12 @@ static int set_opp_max(int new_opp_max)
 static int set_opp_max_ceil(int new_opp_max)
 {
        opp_max_ceil = new_opp_max;
-       return set_opp_max(new_opp_max);
+       return set_mpu_opp_max(new_opp_max);
 }
 
-static int set_cpu_mhz_max(unsigned long new_mhz_max)
+static int set_mpu_mhz_max(unsigned long new_mhz_max)
 {
        unsigned long cur_mhz_max = 0;
-       struct device *mpu_dev;
        int index, ret;
 
        new_mhz_max *= 1000000;
@@ -84,20 +100,15 @@ static int set_cpu_mhz_max(unsigned long new_mhz_max)
                        __func__, opp_max_ceil);
                return -EINVAL;
        }
-       index = opp_max_ceil - 1;
 
-       /* find the minimum opp needed for this clock
-        * and make it max allowed for cpufreq */
-       while (index > 0 && new_mhz_max <= nominal_freqs[index - 1])
+       /* determine minimum OPP needed for given MPU clock limit,
+        * and limit that opp as maximum OPP.
+        * This is for cpufreq governors only. */
+       index = opp_max_ceil - 1;
+       while (index > 0 && new_mhz_max <= nominal_freqs_mpu[index - 1])
                index--;
-       set_opp_max(index + 1);
 
-       mpu_dev = omap_device_get_by_hwmod_name("mpu");
-       if (IS_ERR(mpu_dev)) {
-               pr_err("%s: mpu device not available (%ld)\n",
-                       __func__, PTR_ERR(mpu_dev));
-               return -ENODEV;
-       }
+       set_mpu_opp_max(index + 1);
 
        opp_hack_get_freq(mpu_dev, index, &cur_mhz_max);
        if (cur_mhz_max == new_mhz_max)
@@ -113,46 +124,133 @@ static int set_cpu_mhz_max(unsigned long new_mhz_max)
        return 0;
 }
 
-static int get_cpu_mhz_max(void)
+static int get_mpu_mhz_max(void)
 {
        unsigned long cur_mhz_max = 0;
-       struct device *mpu_dev;
 
        if (opp_max_now < 1 || opp_max_now > opp_max_avail) {
                pr_err("%s: corrupt opp_max: %d\n", __func__, opp_max_now);
                return -EINVAL;
        }
 
-       mpu_dev = omap_device_get_by_hwmod_name("mpu");
-       if (IS_ERR(mpu_dev)) {
-               pr_err("%s: mpu device not available (%ld)\n",
-                       __func__, PTR_ERR(mpu_dev));
-               return -ENODEV;
-       }
-
        opp_hack_get_freq(mpu_dev, opp_max_now - 1, &cur_mhz_max);
 
        return cur_mhz_max / 1000000;
 }
 
-static int init_opp_hacks(void)
+static void update_iva_opp_limit(int target_mhz)
 {
-       struct device *mpu_dev;
+       int volt_max;
+       int i, ret;
 
-       mpu_dev = omap_device_get_by_hwmod_name("mpu");
-       if (IS_ERR(mpu_dev)) {
-               pr_err("%s: mpu device not available (%ld)\n",
-                       __func__, PTR_ERR(mpu_dev));
-               return -ENODEV;
+       for (i = 0; i < opp_max_ceil - 1; i++) {
+               if (target_mhz * 1000000 <= nominal_freqs_iva[i])
+                       break;
+       }
+
+       if (iva_opp_min == i + 1)
+               return;
+
+       //dev_info(iva_dev, "new IVA OPP %d for clock %d\n",
+       //      i + 1, target_mhz);
+
+       volt_max = iva_voltages[opp_max_avail - 1];
+       volt_max += volt_max * 4 / 100;
+
+       ret = regulator_set_voltage(iva_reg, iva_voltages[i], volt_max);
+       if (ret < 0)
+               dev_warn(iva_dev, "unable to set IVA OPP limits: %d\n", ret);
+       else
+               iva_opp_min = i + 1;
+}
+
+static int set_dsp_mhz_max(unsigned long new_mhz_max)
+{
+       int ret;
+
+       mutex_lock(&iva_lock);
+
+       if (iva_active && new_mhz_max > iva_mhz_max)
+               /* going up.. */
+               update_iva_opp_limit(new_mhz_max);
+
+       ret = clk_set_rate(iva_clk, new_mhz_max * 1000000);
+       if (ret != 0) {
+               dev_warn(iva_dev, "unable to change IVA clock to %lu: %d\n",
+                       new_mhz_max, ret);
+               goto out;
+       }
+
+       if (iva_active && new_mhz_max < iva_mhz_max)
+               /* going down.. */
+               update_iva_opp_limit(new_mhz_max);
+
+       iva_mhz_max = new_mhz_max;
+out:
+       mutex_unlock(&iva_lock);
+
+       return ret;
+}
+
+static int get_dsp_mhz_max(void)
+{
+       return iva_mhz_max;
+}
+
+static void iva_unneeded_work(struct work_struct *work)
+{
+       mutex_lock(&iva_lock);
+
+       update_iva_opp_limit(0);
+       iva_active = 0;
+
+       mutex_unlock(&iva_lock);
+}
+
+/* called from c64_tools */
+void dsp_power_notify(int enable)
+{
+       if (enable) {
+               cancel_delayed_work_sync(&iva_work);
+
+               mutex_lock(&iva_lock);
+
+               if (iva_active) {
+                       mutex_unlock(&iva_lock);
+                       return;
+               }
+
+               /* apply the OPP limit */
+               update_iva_opp_limit(iva_mhz_max);
+               iva_active = 1;
+
+               mutex_unlock(&iva_lock);
        }
+       else {
+               if (!iva_active)
+                       return;
+
+               cancel_delayed_work_sync(&iva_work);
+               schedule_delayed_work(&iva_work, HZ * 2);
+       }
+}
+EXPORT_SYMBOL(dsp_power_notify);
+
+static int init_opp_hacks(void)
+{
+       int iva_init_freq;
+       struct opp *opp;
+       int i, ret;
 
        if (cpu_is_omap3630()) {
-               nominal_freqs = nominal_freqs_36xx;
-               opp_max_avail = sizeof(nominal_freqs_36xx) / sizeof(nominal_freqs_36xx[0]);
+               nominal_freqs_mpu = nominal_f_mpu_36xx;
+               nominal_freqs_iva = nominal_f_iva_36xx;
+               opp_max_avail = sizeof(nominal_f_mpu_36xx) / sizeof(nominal_f_mpu_36xx[0]);
                opp_max_ceil = 2;
        } else if (cpu_is_omap34xx()) {
-               nominal_freqs = nominal_freqs_35xx;
-               opp_max_avail = sizeof(nominal_freqs_35xx) / sizeof(nominal_freqs_35xx[0]);
+               nominal_freqs_mpu = nominal_f_mpu_35xx;
+               nominal_freqs_iva = nominal_f_iva_35xx;
+               opp_max_avail = sizeof(nominal_f_mpu_35xx) / sizeof(nominal_f_mpu_35xx[0]);
                opp_max_ceil = opp_max_avail;
        } else {
                dev_err(mpu_dev, "%s: unsupported CPU\n", __func__);
@@ -160,6 +258,39 @@ static int init_opp_hacks(void)
        }
        opp_max_now = opp_max_ceil;
 
+       for (i = 0; i < opp_max_avail; i++) {
+               /* enable all OPPs for MPU so that cpufreq can find out
+                * maximum voltage to supply to regulator as max */
+               ret = opp_enable_i(mpu_dev, i);
+               if (ret != 0) {
+                       dev_err(mpu_dev, "opp_enable returned %d\n", ret);
+                       return ret;
+               }
+
+               ret = opp_enable_i(iva_dev, i);
+               if (ret != 0) {
+                       dev_err(iva_dev, "opp_enable returned %d\n", ret);
+                       return ret;
+               }
+
+               opp = opp_find_freq_exact(iva_dev, nominal_freqs_iva[i], true);
+               if (IS_ERR(opp)) {
+                       dev_err(iva_dev, "mising opp %d, %lu\n",
+                               i, nominal_freqs_iva[i]);
+                       return PTR_ERR(opp);
+               }
+               iva_voltages[i] = opp_get_voltage(opp);
+       }
+
+       iva_init_freq = nominal_freqs_iva[(i + 1) / 2];
+       ret = clk_set_rate(iva_clk, iva_init_freq);
+       if (ret == 0) {
+               iva_mhz_max = iva_init_freq / 1000000;
+               dev_info(iva_dev, "IVA freq set to %dMHz\n", iva_mhz_max);
+       }
+       else
+               dev_err(iva_dev, "IVA freq set failed: %d\n", ret);
+
        return 0;
 }
 
@@ -245,7 +376,7 @@ static int proc_write_val(struct file *file, const char __user *buffer,
 static int cpu_clk_read(char *page, char **start, off_t off, int count,
                int *eof, void *data)
 {
-       return proc_read_val(page, start, off, count, eof, get_cpu_mhz_max());
+       return proc_read_val(page, start, off, count, eof, get_mpu_mhz_max());
 }
 
 static int cpu_clk_write(struct file *file, const char __user *buffer,
@@ -258,7 +389,30 @@ static int cpu_clk_write(struct file *file, const char __user *buffer,
        if (retval < 0)
                return retval;
 
-       ret = set_cpu_mhz_max(val);
+       ret = set_mpu_mhz_max(val);
+       if (ret < 0)
+               return ret;
+
+       return retval;
+}
+
+static int dsp_clk_read(char *page, char **start, off_t off, int count,
+               int *eof, void *data)
+{
+       return proc_read_val(page, start, off, count, eof, get_dsp_mhz_max());
+}
+
+static int dsp_clk_write(struct file *file, const char __user *buffer,
+               unsigned long count, void *data)
+{
+       unsigned long val;
+       int ret, retval;
+
+       retval = proc_write_val(file, buffer, count, &val);
+       if (retval < 0)
+               return retval;
+
+       ret = set_dsp_mhz_max(val);
        if (ret < 0)
                return ret;
 
@@ -342,26 +496,78 @@ static int pndctrl_init(void)
 {
        int ret;
 
+       INIT_DELAYED_WORK(&iva_work, iva_unneeded_work);
+
+       mpu_dev = omap_device_get_by_hwmod_name("mpu");
+       if (IS_ERR(mpu_dev)) {
+               pr_err("%s: mpu device not available (%ld)\n",
+                       __func__, PTR_ERR(mpu_dev));
+               return -ENODEV;
+       }
+
+       iva_dev = omap_device_get_by_hwmod_name("iva");
+       if (IS_ERR(iva_dev)) {
+               pr_err("%s: iva device not available (%ld)\n",
+                       __func__, PTR_ERR(iva_dev));
+               return -ENODEV;
+       }
+
+       /* regulator to constrain OPPs while DSP is running */
+       iva_reg = regulator_get(iva_dev, "vcc");
+       if (IS_ERR(iva_reg)) {
+               dev_err(iva_dev, "unable to get MPU regulator\n");
+               return -ENODEV;
+       }
+
+       /* 
+        * Ensure physical regulator is present.
+        * (e.g. could be dummy regulator.)
+        */
+       if (regulator_get_voltage(iva_reg) < 0) {
+               dev_err(iva_dev, "IVA regulator is not physical?\n");
+               ret = -ENODEV;
+               goto fail_reg;
+       }
+
+       iva_clk = clk_get(NULL, "dpll2_ck");
+       if (IS_ERR(iva_clk)) {
+               dev_err(iva_dev, "IVA clock not available.\n");
+               ret = PTR_ERR(iva_clk);
+               goto fail_reg;
+       }
+
        ret = init_opp_hacks();
        if (ret != 0) {
                pr_err("init_opp_hacks failed: %d\n", ret);
-               return -EFAULT;
+               goto fail_opp;
        }
 
        proc_create_rw(PROC_CPUMHZ, NULL, cpu_clk_read, cpu_clk_write);
+       proc_create_rw(PROC_DSPMHZ, NULL, dsp_clk_read, dsp_clk_write);
        proc_create_rw(PROC_CPUOPP, NULL, cpu_maxopp_read, cpu_maxopp_write);
        proc_create_rw(PROC_SYSMHZ, NULL, sys_clk_read, sys_clk_write);
 
        pr_info("OMAP overclocker loaded.\n");
        return 0;
+
+fail_opp:
+       clk_put(iva_clk);
+fail_reg:
+       regulator_put(iva_reg);
+       return ret;
 }
 
 
 static void pndctrl_cleanup(void)
 {
+       remove_proc_entry(PROC_SYSMHZ, NULL);
        remove_proc_entry(PROC_CPUOPP, NULL);
+       remove_proc_entry(PROC_DSPMHZ, NULL);
        remove_proc_entry(PROC_CPUMHZ, NULL);
-       remove_proc_entry(PROC_SYSMHZ, NULL);
+
+       cancel_delayed_work_sync(&iva_work);
+       regulator_put(iva_reg);
+       clk_put(iva_clk);
 }
 
 module_init(pndctrl_init);