summaryrefslogtreecommitdiff
path: root/arch/arm/mach-wmt/wmt_cpufreq.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-wmt/wmt_cpufreq.c')
-rw-r--r--arch/arm/mach-wmt/wmt_cpufreq.c752
1 files changed, 752 insertions, 0 deletions
diff --git a/arch/arm/mach-wmt/wmt_cpufreq.c b/arch/arm/mach-wmt/wmt_cpufreq.c
new file mode 100644
index 00000000..f5046f40
--- /dev/null
+++ b/arch/arm/mach-wmt/wmt_cpufreq.c
@@ -0,0 +1,752 @@
+/*++
+ linux/arch/arm/mach-wmt/wmt_cpufreq.c
+
+ Copyright (c) 2013 WonderMedia Technologies, Inc.
+
+ This program is free software: you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software Foundation,
+ either version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see <http://www.gnu.org/licenses/>.
+
+ WonderMedia Technologies, Inc.
+ 10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C.
+--*/
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/cpufreq.h>
+#include <linux/delay.h>
+#include <linux/reboot.h>
+#include <linux/syscalls.h>
+#include <linux/suspend.h>
+#include <asm/smp_plat.h>
+#include <asm/cpu.h>
+#include <linux/regulator/consumer.h>
+
+#include <mach/wmt_env.h>
+#include <mach/hardware.h>
+
+/*
+#define DEBUG
+*/
+#ifdef DEBUG
+static int dbg_mask = 1;
+module_param(dbg_mask, int, S_IRUGO | S_IWUSR);
+#define fq_dbg(fmt, args...) \
+ do {\
+ if (dbg_mask) \
+ printk(KERN_ERR "[%s]_%d_%d: " fmt, __func__ , __LINE__, smp_processor_id(), ## args);\
+ } while (0)
+#define fq_trace() \
+ do {\
+ if (dbg_mask) \
+ printk(KERN_ERR "trace in %s %d\n", __func__, __LINE__);\
+ } while (0)
+#else
+#define fq_dbg(fmt, args...)
+#define fq_trace()
+#endif
+
+#define DVFS_TABLE_NUM_MAX 20
+#define DVFS_TABLE_NUM_MIN 2
+#define PMC_BASE PM_CTRL_BASE_ADDR
+#define PLLA_FREQ_KHZ (24 * 1000)
+#define PMC_PLLA (PM_CTRL_BASE_ADDR + 0x200)
+#define ARM_DIV_OFFSET (PM_CTRL_BASE_ADDR + 0x300)
+#define MILLISEC_TO_MICROSEC 1000
+
+struct wmt_dvfs_table {
+ unsigned int freq;
+ unsigned int vol;
+ unsigned int l2c_div;
+ unsigned int l2c_tag;
+ unsigned int l2c_data;
+ unsigned int axi;
+ int index;
+ struct list_head node;
+};
+
+struct wmt_dvfs_driver_data {
+ unsigned int tbl_num;
+ unsigned int sample_rate;
+ struct list_head wmt_dvfs_list;
+ struct wmt_dvfs_table *dvfs_table;
+ struct cpufreq_frequency_table *freq_table;
+};
+static struct wmt_dvfs_driver_data wmt_dvfs_drvdata;
+static struct regulator *re;
+static unsigned int use_regulator = 0;
+
+char use_dvfs;/*flag for read env varalbe*/
+static char use_dvfs_debug;
+static int wmt_dvfs_running;/*flag for reboot disable dvfs*/
+static DEFINE_SEMAPHORE(wmt_cpufreq_sem);
+
+/* register a reboot notifier, invoked in kernel_restart_prepare()
+ * or kernel_shutdown_prepare() */
+static int wmt_dvfs_reboot(struct notifier_block *, unsigned long, void *);
+static struct notifier_block wmt_reboot_notifier = {
+ .notifier_call = wmt_dvfs_reboot,
+};
+
+static int wmt_suspend_target(unsigned target, unsigned relation, unsigned is_suspend);
+static int wmt_dvfs_reboot(struct notifier_block *nb, unsigned long event, void *unused)
+{
+ unsigned target = 0xFFFFFFFF;
+ struct cpufreq_policy *policy = cpufreq_cpu_get(0); /* boot CPU */
+
+ /* find the highest freq in table */
+ if (policy)
+ target = policy->max;
+
+ /* change freq to highest to make sure reboot use the max volatage */
+ wmt_suspend_target(target, CPUFREQ_RELATION_H, 0);
+
+ return NOTIFY_OK;
+}
+
+static int wmt_init_cpufreq_table(struct wmt_dvfs_driver_data *drv_data)
+{
+ int i = 0;
+ struct wmt_dvfs_table *dvfs_tbl = NULL;
+ struct cpufreq_frequency_table *freq_tbl = NULL;
+
+ freq_tbl = kzalloc(sizeof(struct cpufreq_frequency_table) * (drv_data->tbl_num + 1),
+ GFP_KERNEL);
+ if (freq_tbl == NULL) {
+ printk(KERN_ERR "%s: failed to allocate frequency table\n", __func__);
+ return -ENOMEM;
+ }
+
+ drv_data->freq_table = freq_tbl;
+ list_for_each_entry(dvfs_tbl, &drv_data->wmt_dvfs_list, node) {
+ fq_dbg("freq_table[%d]:freq->%dKhz", i, dvfs_tbl->freq);
+ freq_tbl[i].index = i;
+ freq_tbl[i].frequency = dvfs_tbl->freq;
+ i++;
+ }
+ /* the last element must be initialized to CPUFREQ_TABLE_END */
+ freq_tbl[i].index = i;
+ freq_tbl[i].frequency = CPUFREQ_TABLE_END;
+
+ return 0;
+}
+
+/* wmt_getspeed return unit is Khz */
+static unsigned int wmt_getspeed(unsigned int cpu)
+{
+ int freq = 0;
+
+ if (cpu >= NR_CPUS)
+ return 0;
+
+ freq = auto_pll_divisor(DEV_ARM, GET_FREQ, 0, 0) / 1000;
+
+ if (freq < 0)
+ freq = 0;
+
+ return freq;
+}
+
+static struct wmt_dvfs_table *find_freq_ceil(unsigned int *target)
+{
+ struct wmt_dvfs_table *dvfs_tbl = NULL;
+ unsigned int freq = *target;
+
+ list_for_each_entry(dvfs_tbl, &wmt_dvfs_drvdata.wmt_dvfs_list, node) {
+ if (dvfs_tbl->freq >= freq) {
+ *target = dvfs_tbl->freq;
+ return dvfs_tbl;
+ }
+ }
+
+ return NULL;
+}
+
+static struct wmt_dvfs_table *find_freq_floor(unsigned int *target)
+{
+ struct wmt_dvfs_table *dvfs_tbl = NULL;
+ unsigned int freq = *target;
+
+ list_for_each_entry_reverse(dvfs_tbl, &wmt_dvfs_drvdata.wmt_dvfs_list, node) {
+ if (dvfs_tbl->freq <= freq) {
+ *target = dvfs_tbl->freq;
+ return dvfs_tbl;
+ }
+ }
+
+ return NULL;
+}
+
+static struct wmt_dvfs_table *
+wmt_recalc_target_freq(unsigned *target_freq, unsigned relation)
+{
+ struct wmt_dvfs_table *dvfs_tbl = NULL;
+
+ if (!target_freq)
+ return NULL;
+
+ switch (relation) {
+ case CPUFREQ_RELATION_L:
+ /* Try to select a new_freq higher than or equal target_freq */
+ dvfs_tbl = find_freq_ceil(target_freq);
+ break;
+ case CPUFREQ_RELATION_H:
+ /* Try to select a new_freq lower than or equal target_freq */
+ dvfs_tbl = find_freq_floor(target_freq);
+ break;
+ default:
+ dvfs_tbl = NULL;
+ break;
+ }
+
+ return dvfs_tbl;
+}
+
+/* this is a debug func, for checking pmc divf, divr, divq value */
+static int get_arm_plla_param(void)
+{
+#ifdef DEBUG
+ unsigned ft, df, dr, dq, div, tmp;
+
+ tmp = *(volatile unsigned int *)PMC_PLLA;
+ fq_dbg("PMC PLLA REG IS 0x%08x\n", tmp);
+
+ ft = (tmp >> 24) & 0x03; /*bit24 ~ bit26*/
+ df = (tmp >> 16) & 0xFF; /*bit16 ~ bit23*/
+ dr = (tmp >> 8) & 0x1F; /*bit8 ~ bit12*/
+ dq = tmp & 0x03;
+
+ tmp = *(volatile unsigned int *)ARM_DIV_OFFSET;
+ div = tmp & 0x1F;
+
+ fq_dbg("ft:%d, df:%d, dr:%d, dq:%d, div:%d\n", ft, df, dr, dq, div);
+#endif
+ return 0;
+}
+
+static int wmt_change_plla_table(struct wmt_dvfs_table *plla_table, unsigned relation)
+{
+ int ret = 0;
+ int ret_vol = 0;
+ struct plla_param plla_env;
+
+ plla_env.plla_clk = (plla_table->freq / 1000);
+ plla_env.arm_div = 1;
+ plla_env.l2c_div = plla_table->l2c_div;
+ plla_env.l2c_tag_div = plla_table->l2c_tag;
+ plla_env.l2c_data_div = plla_table->l2c_data;
+ plla_env.axi_div = plla_table->axi;
+ plla_env.tb_index = plla_table->index;
+
+ switch (relation) {
+ case CPUFREQ_RELATION_L:
+ if (use_dvfs_debug)
+ printk(KERN_INFO "change to L %dKhz\n", plla_table->freq);
+ ret = set_plla_divisor(&plla_env);
+ if (plla_table->vol) {
+ if (use_dvfs_debug)
+ printk(KERN_INFO "pllal_table = %d\n", plla_table->vol*1000);
+ if (use_regulator) {
+ ret_vol = regulator_set_voltage(re, plla_table->vol*1000, plla_table->vol*1000);
+ if (ret_vol < 0)
+ printk(KERN_INFO "xxx set vol fail %d\n", ret_vol);
+ }
+ }
+ break;
+ case CPUFREQ_RELATION_H:
+ if ((use_regulator == 1) && (regulator_is_enabled(re) < 0)) {
+ if (use_dvfs_debug)
+ printk(KERN_INFO "xxx regulator is disabled\n");
+ ret = -EINVAL;
+ } else {
+ if (plla_table->vol) {
+ if (use_dvfs_debug)
+ printk(KERN_INFO "pllah_table = %d\n", plla_table->vol*1000);
+ if (use_regulator)
+ ret = regulator_set_voltage(re, plla_table->vol*1000, plla_table->vol*1000);
+ }
+ if (ret >= 0) {
+ if (use_dvfs_debug)
+ printk(KERN_INFO "change to H %dKhz\n", plla_table->freq);
+ ret = set_plla_divisor(&plla_env);
+ } else
+ printk(KERN_INFO "xxx set vol fail %d\n", ret);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/* target is calculated by cpufreq governor, unit Khz */
+static int freq_save = 0; // unit Khz
+static int wmt_target(struct cpufreq_policy *, unsigned, unsigned);
+static int wmt_suspend_target(unsigned target, unsigned relation, unsigned is_suspend)
+{
+ unsigned int cur_freq = wmt_getspeed(0);
+ int voltage = 0;
+
+ down(&wmt_cpufreq_sem);
+
+ /*
+ * Set to the highest voltage before suspend/reboot
+ */
+ if (use_regulator) {
+ if (use_dvfs_debug)
+ printk("Set to Max. Voltage: %dmV\n", wmt_dvfs_drvdata.dvfs_table[(wmt_dvfs_drvdata.tbl_num - 1)].vol);
+ regulator_set_voltage(re, wmt_dvfs_drvdata.dvfs_table[(wmt_dvfs_drvdata.tbl_num - 1)].vol * 1000,
+ wmt_dvfs_drvdata.dvfs_table[(wmt_dvfs_drvdata.tbl_num - 1)].vol * 1000);
+ voltage = regulator_get_voltage(re);
+ printk("Current voltage = %d\n", voltage);
+ }
+ /* for some governor, like userspace, performance, powersaving,
+ * need change frequency to pre-suspend when resume */
+ wmt_dvfs_running = 0;
+ freq_save = cur_freq;
+ up(&wmt_cpufreq_sem);
+
+ return 0;
+}
+
+/*
+ * Note that loops_per_jiffy is not updated on SMP systems in
+ * cpufreq driver. So, update the per-CPU loops_per_jiffy value
+ * on frequency transition. We need to update all dependent CPUs.
+ */
+#ifdef CONFIG_SMP
+struct lpj_info {
+ unsigned long ref;
+ unsigned int freq;
+};
+static DEFINE_PER_CPU(struct lpj_info, lpj_ref);
+static struct lpj_info global_lpj_ref;
+
+static void
+wmt_update_lpj(struct cpufreq_policy *policy, struct cpufreq_freqs freqs)
+{
+ unsigned int i = 0;
+
+ for_each_cpu(i, policy->cpus) {
+ struct lpj_info *lpj = &per_cpu(lpj_ref, i);
+ if (!lpj->freq) {
+ lpj->ref = per_cpu(cpu_data, i).loops_per_jiffy;
+ lpj->freq = freqs.old;
+ }
+ per_cpu(cpu_data, i).loops_per_jiffy = cpufreq_scale(lpj->ref,
+ lpj->freq, freqs.new);
+ }
+ /* And don't forget to adjust the global one */
+ if (!global_lpj_ref.freq) {
+ global_lpj_ref.ref = loops_per_jiffy;
+ global_lpj_ref.freq = freqs.old;
+ }
+ loops_per_jiffy = cpufreq_scale(global_lpj_ref.ref,
+ global_lpj_ref.freq, freqs.new);
+}
+#else
+static void wmt_update_lpj(struct cpufreq_policy *policy, struct cpufreq_freqs freqs)
+{
+ return ;
+}
+#endif
+
+/* target is calculated by cpufreq governor, unit Khz */
+static int
+wmt_target(struct cpufreq_policy *policy, unsigned target, unsigned relation)
+{
+ int ret = 0;
+ unsigned int i = 0;
+ struct cpufreq_freqs freqs;
+ struct wmt_dvfs_table *dvfs_tbl = NULL;
+
+ if (policy->cpu > NR_CPUS)
+ return -EINVAL;
+
+ fq_dbg("cpu freq:%dMhz now, need %s to %dMhz\n", wmt_getspeed(policy->cpu) / 1000,
+ (relation == CPUFREQ_RELATION_L) ? "DOWN" : "UP", target / 1000);
+ if (use_regulator) {
+ if (regulator_is_enabled(re) < 0) {
+ ret = -EBUSY;
+ fq_dbg("regulator is disabled\n\n");
+ goto out;
+ }
+ }
+
+ /* Ensure desired rate is within allowed range. Some govenors
+ * (ondemand) will just pass target_freq=0 to get the minimum. */
+
+ if ((strncmp(policy->governor->name, "performance", CPUFREQ_NAME_LEN)) && (wmt_dvfs_drvdata.tbl_num > 2))
+ {
+ if (policy->max >= wmt_dvfs_drvdata.dvfs_table[(wmt_dvfs_drvdata.tbl_num - 1)].freq)
+ policy->max = wmt_dvfs_drvdata.dvfs_table[(wmt_dvfs_drvdata.tbl_num - 2)].freq;
+ if (policy->min >= wmt_dvfs_drvdata.dvfs_table[(wmt_dvfs_drvdata.tbl_num - 1)].freq)
+ policy->min = wmt_dvfs_drvdata.dvfs_table[(wmt_dvfs_drvdata.tbl_num - 2)].freq;
+ }
+
+ if (target < policy->min)
+ target = policy->min;
+ if (target > policy->max)
+ target = policy->max;
+
+ /* find out (freq, voltage) pair to do dvfs */
+ dvfs_tbl = wmt_recalc_target_freq(&target, relation);
+ if (dvfs_tbl == NULL) {
+ fq_dbg("Can not change to target_freq:%dKhz", target);
+ ret = -EINVAL;
+ goto out;
+ }
+ fq_dbg("recalculated target freq is %dMhz\n", target / 1000);
+
+ freqs.cpu = policy->cpu;
+ freqs.new = target;
+ freqs.old = wmt_getspeed(policy->cpu);
+
+ if (freqs.new == freqs.old) {
+ ret = 0;
+ goto out;
+ } else if (freqs.new > freqs.old)
+ relation = CPUFREQ_RELATION_H;
+ else
+ relation = CPUFREQ_RELATION_L;
+
+ /* notifier for all cpus */
+ for_each_cpu(i, policy->cpus) {
+ freqs.cpu = i;
+ cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
+ }
+
+ /* actually we just scaling CPU frequency here */
+ get_arm_plla_param();
+ ret = wmt_change_plla_table(dvfs_tbl, relation);
+ get_arm_plla_param();
+ fq_dbg("change to %dKhz\n", ret / 1000);
+
+ if (ret < 0) {
+ ret = -EFAULT;
+ fq_dbg("wmt_cpufreq: auto_pll_divisor failed\n");
+ } else {
+ wmt_update_lpj(policy, freqs);
+
+ for_each_cpu(i, policy->cpus) {
+ freqs.cpu = i;
+ cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
+ }
+ }
+
+out:
+ fq_dbg("cpu freq scaled to %dMhz now\n\n", wmt_getspeed(policy->cpu) / 1000);
+ return ret;
+}
+
+static int
+wmt_dvfs_target(struct cpufreq_policy *policy, unsigned target, unsigned relation)
+{
+ int ret = 0;
+
+ down(&wmt_cpufreq_sem);
+
+ if (!wmt_dvfs_running) {
+ ret = -ENODEV;
+ fq_dbg("dvfs is not running now!\n");
+ goto out;
+ }
+
+ ret = wmt_target(policy, target, relation);
+
+out:
+ up(&wmt_cpufreq_sem);
+ fq_dbg("cpu freq scaled to %dMhz now\n\n", wmt_getspeed(policy->cpu) / 1000);
+ return ret;
+}
+
+
+static int
+wmt_cpufreq_pm_notify(struct notifier_block *nb, unsigned long event, void *dummy)
+{
+ struct cpufreq_policy *policy = cpufreq_cpu_get(0);
+ int default_voltage = 0;
+ struct plla_param plla_env;
+ struct wmt_dvfs_table *plla_table = NULL;
+
+ plla_table = &wmt_dvfs_drvdata.dvfs_table[0];
+
+ plla_env.plla_clk = (plla_table->freq / 1000);
+ plla_env.arm_div = 1;
+ plla_env.l2c_div = plla_table->l2c_div;
+ plla_env.l2c_tag_div = plla_table->l2c_tag;
+ plla_env.l2c_data_div = plla_table->l2c_data;
+ plla_env.axi_div = plla_table->axi;
+ plla_env.tb_index = plla_table->index;
+
+ if (event == PM_SUSPEND_PREPARE)
+ wmt_suspend_target(0, CPUFREQ_RELATION_L, 1);
+ else if (event == PM_POST_SUSPEND) {
+ if (use_regulator) {
+ default_voltage = regulator_get_voltage(re);
+ if (use_dvfs_debug)
+ printk("default_voltage = %d\n", default_voltage);
+ regulator_set_voltage(re, default_voltage, default_voltage);
+ if (use_dvfs_debug)
+ printk("Set Min. Freq = %d\n", plla_env.plla_clk);
+ set_plla_divisor(&plla_env);
+ }
+ down(&wmt_cpufreq_sem);
+ wmt_dvfs_running = 1;
+ wmt_target(policy, freq_save, CPUFREQ_RELATION_H);
+ up(&wmt_cpufreq_sem);
+ /* when resume back, changed to suspended freq */
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block wmt_cpu_pm_notifier = {
+ .notifier_call = wmt_cpufreq_pm_notify,
+};
+
+static int wmt_verify_speed(struct cpufreq_policy *policy)
+{
+ int ret = 0;
+ struct cpufreq_frequency_table *freq_tbl = wmt_dvfs_drvdata.freq_table;
+
+ if (policy->cpu >= NR_CPUS)
+ return -EINVAL;
+
+ if (NULL == freq_tbl)
+ ret = -EINVAL;
+ else
+ ret = cpufreq_frequency_table_verify(policy, freq_tbl);
+
+ return ret;
+}
+
+static int wmt_cpu_init(struct cpufreq_policy *policy)
+{
+ int ret = 0;
+ struct cpufreq_frequency_table *wmt_freq_tbl = NULL;
+
+ fq_dbg("CPU%d cpufreq init\n", policy->cpu);
+ if (policy->cpu >= NR_CPUS)
+ return -EINVAL;
+
+ wmt_freq_tbl = wmt_dvfs_drvdata.freq_table;
+ if (!wmt_freq_tbl)
+ return -EINVAL;
+
+ policy->cur = wmt_getspeed(policy->cpu);
+ policy->min = wmt_getspeed(policy->cpu);
+ policy->max = wmt_getspeed(policy->cpu);
+
+ /*
+ * check each frequency and find max_freq
+ * min_freq in the table, then set:
+ * policy->min = policy->cpuinfo.min_freq = min_freq;
+ * policy->max = policy->cpuinfo.max_freq = max_freq;
+ */
+ ret = cpufreq_frequency_table_cpuinfo(policy, wmt_freq_tbl);
+ if (0 == ret)
+ cpufreq_frequency_table_get_attr(wmt_freq_tbl, policy->cpu);
+
+ /*
+ * On ARM-CortaxA9 configuartion, both processors share the voltage
+ * and clock. So both CPUs needs to be scaled together and hence
+ * needs software co-ordination. Use cpufreq affected_cpus
+ * interface to handle this scenario. Additional is_smp() check
+ * is to keep SMP_ON_UP build working.
+ */
+ if (is_smp()) {
+ policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
+ cpumask_setall(policy->cpus);
+ }
+ /* only CPU0 can enable dvfs when cpufreq init.
+ * when cpu hotplug enable, cpu1 will re-init when plugin */
+ if (!policy->cpu) {
+ down(&wmt_cpufreq_sem);
+ wmt_dvfs_running = 1;
+ wmt_target(policy, policy->max, CPUFREQ_RELATION_H);
+ up(&wmt_cpufreq_sem);
+ }
+ /*
+ * 1. make sure current frequency be covered in cpufreq_table
+ * 2. change cpu frequency to policy-max for fast booting
+ */
+
+ policy->cur = wmt_getspeed(policy->cpu);
+ policy->cpuinfo.transition_latency = wmt_dvfs_drvdata.sample_rate;
+
+ fq_dbg("CPU%d,p->max:%d, p->min:%d, c->max=%d, c->min=%d, current:%d\n",
+ policy->cpu, policy->max, policy->min, policy->cpuinfo.max_freq,
+ policy->cpuinfo.min_freq, wmt_getspeed(policy->cpu));
+
+ return ret;
+}
+
+static struct freq_attr *wmt_cpufreq_attr[] = {
+ &cpufreq_freq_attr_scaling_available_freqs,
+ NULL,
+};
+
+static struct cpufreq_driver wmt_cpufreq_driver = {
+ .name = "wmt_cpufreq",
+ .owner = THIS_MODULE,
+ .flags = CPUFREQ_STICKY,
+ .init = wmt_cpu_init,
+ .verify = wmt_verify_speed,
+ .target = wmt_dvfs_target,
+ .get = wmt_getspeed,
+ .attr = wmt_cpufreq_attr,
+};
+
+static int __init wmt_cpufreq_check_env(void)
+{
+ int i = 0;
+ int ret = 0;
+ int varlen = 512;
+ char *ptr = NULL;
+ unsigned int tbl_num = 0;
+ unsigned int drv_en = 0;
+ unsigned int sample_rate = 0;
+ unsigned int freq = 0;
+ unsigned int voltage = 0;
+ unsigned int l2c_div = 0;
+ unsigned int l2c_tag = 0;
+ unsigned int l2c_data = 0;
+ unsigned int axi = 0;
+ struct wmt_dvfs_table *wmt_dvfs_tbl = NULL;
+ unsigned char buf[512] = {0};
+ char f_is_bonding = 0;
+
+ /* uboot env name is: wmt.cpufreq.param, format is:
+ <enable>:<sample_rate>:<table_number>:<[freq,voltage,l2c_div,l2c_tag,l2c_data,axi]
+ */
+
+ use_dvfs = 0;
+ use_dvfs_debug = 0;
+
+ if (BONDING_OPTION_4BYTE_VAL & 0x8)
+ f_is_bonding = 1;
+
+ if (f_is_bonding)
+ ret = wmt_getsyspara("wmt.cpufreqlp.param", buf, &varlen);
+ else
+ ret = wmt_getsyspara("wmt.cpufreq.param", buf, &varlen);
+
+ if (ret) {
+ printk(KERN_INFO "Can not find uboot env wmt.cpufreq.param\n");
+ ret = -ENODATA;
+ goto out;
+ }
+ fq_dbg("wmt.cpufreq.param:%s\n", buf);
+
+ sscanf(buf, "%x:%d:%d", &drv_en, &sample_rate, &tbl_num);
+ if (!drv_en) {
+ printk(KERN_INFO "wmt cpufreq disaled\n");
+ ret = -ENODEV;
+ goto out;
+ }
+
+ /* 2 dvfs table at least */
+ if (tbl_num < DVFS_TABLE_NUM_MIN) {
+ printk(KERN_INFO "No frequency information found\n");
+ ret = -ENODATA;
+ goto out;
+ }
+ if (tbl_num > DVFS_TABLE_NUM_MAX)
+ tbl_num = DVFS_TABLE_NUM_MAX;
+
+ wmt_dvfs_tbl = kzalloc(sizeof(struct wmt_dvfs_table) * tbl_num, GFP_KERNEL);
+ if (!wmt_dvfs_tbl) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ wmt_dvfs_drvdata.tbl_num = tbl_num;
+ wmt_dvfs_drvdata.sample_rate = sample_rate * MILLISEC_TO_MICROSEC;
+ wmt_dvfs_drvdata.dvfs_table = wmt_dvfs_tbl;
+ INIT_LIST_HEAD(&wmt_dvfs_drvdata.wmt_dvfs_list);
+
+ /* copy freq&vol info from uboot env to wmt_dvfs_table */
+ ptr = buf;
+ for (i = 0; i < tbl_num; i++) {
+ strsep(&ptr, "[");
+ sscanf(ptr, "%d,%d,%d,%d,%d,%d]:[", &freq, &voltage, &l2c_div, &l2c_tag, &l2c_data, &axi);
+ wmt_dvfs_tbl[i].freq = freq*1000;
+ wmt_dvfs_tbl[i].vol = voltage;
+ wmt_dvfs_tbl[i].l2c_div = l2c_div;
+ wmt_dvfs_tbl[i].l2c_tag = l2c_tag;
+ wmt_dvfs_tbl[i].l2c_data = l2c_data;
+ wmt_dvfs_tbl[i].axi = axi;
+ wmt_dvfs_tbl[i].index = i;
+ INIT_LIST_HEAD(&wmt_dvfs_tbl[i].node);
+ list_add_tail(&wmt_dvfs_tbl[i].node, &wmt_dvfs_drvdata.wmt_dvfs_list);
+ fq_dbg("dvfs_table[%d]: freq %dMhz, voltage %dmV l2c_div %d l2c_tag %d l2c_data %d axi %d\n",
+ i, freq, voltage, l2c_div, l2c_tag, l2c_data, axi);
+// printk("dvfs_table[%d]: freq %dMhz, voltage %dmV l2c_div %d l2c_tag %d l2c_data %d axi %d \n",
+// i, freq, voltage, l2c_div, l2c_tag, l2c_data, axi);//gri
+ }
+ use_dvfs = 1;
+
+ if (drv_en & 0x10)
+ use_dvfs_debug = 1;
+
+out:
+ return ret;
+}
+
+static int __init wmt_cpufreq_driver_init(void)
+{
+ int ret = 0;
+ unsigned int chip_id = 0;
+ unsigned int bondingid = 0;
+ struct cpufreq_frequency_table *wmt_freq_tbl = NULL;
+
+ wmt_dvfs_running = 0;
+ sema_init(&wmt_cpufreq_sem, 1);
+ wmt_getsocinfo(&chip_id, &bondingid);
+
+ /* if cpufreq disabled, cpu will always run at current frequency
+ * which defined in wmt.plla.param */
+ ret = wmt_cpufreq_check_env();
+ if (ret) {
+ printk(KERN_WARNING "wmt_cpufreq check env failed, current cpu "
+ "frequency is %dKhz\n", wmt_getspeed(0));
+ goto out;
+ }
+ /* copy dvfs info from uboot to cpufreq table,
+ generate cpu frequency table here */
+ wmt_init_cpufreq_table(&wmt_dvfs_drvdata);
+ wmt_freq_tbl = wmt_dvfs_drvdata.freq_table;
+ if (NULL == wmt_freq_tbl) {
+ printk(KERN_ERR "wmt_cpufreq create wmt_freq_tbl failed\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ register_reboot_notifier(&wmt_reboot_notifier);
+ register_pm_notifier(&wmt_cpu_pm_notifier);
+
+ use_regulator = 1;
+ re = regulator_get(NULL, "wmt_corepower");
+ if (IS_ERR(re))
+ use_regulator = 0;
+ printk("[CPU-FREQ]use_regulator = %d\n", use_regulator);
+ ret = cpufreq_register_driver(&wmt_cpufreq_driver);
+
+out:
+ return ret;
+}
+late_initcall(wmt_cpufreq_driver_init);
+
+MODULE_AUTHOR("WonderMedia Technologies, Inc");
+MODULE_DESCRIPTION("WMT CPU frequency driver");
+MODULE_LICENSE("Dual BSD/GPL");