| /* |
| * linux/arch/arm/mach-omap2/resource34xx.c |
| * OMAP3 resource init/change_level/validate_level functions |
| * |
| * Copyright (C) 2007-2008 Texas Instruments, Inc. |
| * Rajendra Nayak <rnayak@ti.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED |
| * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. |
| * History: |
| * |
| */ |
| |
| #include <linux/pm_qos_params.h> |
| #include <linux/cpufreq.h> |
| #include <linux/delay.h> |
| #include <mach/powerdomain.h> |
| #include <mach/clockdomain.h> |
| #include <mach/control.h> |
| #include <mach/omap34xx.h> |
| #include "smartreflex.h" |
| #include "resource34xx.h" |
| #include "pm.h" |
| #include "cm.h" |
| #include "cm-regbits-34xx.h" |
| |
| /** |
| * init_latency - Initializes the mpu/core latency resource. |
| * @resp: Latency resource to be initalized |
| * |
| * No return value. |
| */ |
| void init_latency(struct shared_resource *resp) |
| { |
| resp->no_of_users = 0; |
| resp->curr_level = RES_DEFAULTLEVEL; |
| *((u8 *)resp->resource_data) = 0; |
| return; |
| } |
| |
| /** |
| * set_latency - Adds/Updates and removes the CPU_DMA_LATENCY in *pm_qos_params. |
| * @resp: resource pointer |
| * @latency: target latency to be set |
| * |
| * Returns 0 on success, or error values as returned by |
| * pm_qos_update_requirement/pm_qos_add_requirement. |
| */ |
| int set_latency(struct shared_resource *resp, u32 latency) |
| { |
| u8 *pm_qos_req_added; |
| |
| if (resp->curr_level == latency) |
| return 0; |
| else |
| /* Update the resources current level */ |
| resp->curr_level = latency; |
| |
| pm_qos_req_added = resp->resource_data; |
| if (latency == RES_DEFAULTLEVEL) |
| /* No more users left, remove the pm_qos_req if present */ |
| if (*pm_qos_req_added) { |
| pm_qos_remove_requirement(PM_QOS_CPU_DMA_LATENCY, |
| resp->name); |
| *pm_qos_req_added = 0; |
| return 0; |
| } |
| |
| if (*pm_qos_req_added) { |
| return pm_qos_update_requirement(PM_QOS_CPU_DMA_LATENCY, |
| resp->name, latency); |
| } else { |
| *pm_qos_req_added = 1; |
| return pm_qos_add_requirement(PM_QOS_CPU_DMA_LATENCY, |
| resp->name, latency); |
| } |
| } |
| |
| /** |
| * init_pd_latency - Initializes the power domain latency resource. |
| * @resp: Power Domain Latency resource to be initialized. |
| * |
| * No return value. |
| */ |
| void init_pd_latency(struct shared_resource *resp) |
| { |
| struct pd_latency_db *pd_lat_db; |
| |
| resp->no_of_users = 0; |
| if (enable_off_mode) |
| resp->curr_level = PD_LATENCY_OFF; |
| else |
| resp->curr_level = PD_LATENCY_RET; |
| pd_lat_db = resp->resource_data; |
| /* Populate the power domain associated with the latency resource */ |
| pd_lat_db->pd = pwrdm_lookup(pd_lat_db->pwrdm_name); |
| set_pwrdm_state(pd_lat_db->pd, resp->curr_level); |
| return; |
| } |
| |
| /** |
| * set_pd_latency - Updates the curr_level of the power domain resource. |
| * @resp: Power domain latency resource. |
| * @latency: New latency value acceptable. |
| * |
| * This function maps the latency in microsecs to the acceptable |
| * Power domain state using the latency DB. |
| * It then programs the power domain to enter the target state. |
| * Always returns 0. |
| */ |
| int set_pd_latency(struct shared_resource *resp, u32 latency) |
| { |
| u32 pd_lat_level, ind; |
| struct pd_latency_db *pd_lat_db; |
| struct powerdomain *pwrdm; |
| |
| pd_lat_db = resp->resource_data; |
| pwrdm = pd_lat_db->pd; |
| pd_lat_level = PD_LATENCY_OFF; |
| /* using the latency db map to the appropriate PD state */ |
| for (ind = 0; ind < PD_LATENCY_MAXLEVEL; ind++) { |
| if (pd_lat_db->latency[ind] < latency) { |
| pd_lat_level = ind; |
| break; |
| } |
| } |
| |
| if (!enable_off_mode && pd_lat_level == PD_LATENCY_OFF) |
| pd_lat_level = PD_LATENCY_RET; |
| |
| resp->curr_level = pd_lat_level; |
| set_pwrdm_state(pwrdm, pd_lat_level); |
| return 0; |
| } |
| |
| static struct shared_resource *vdd1_resp; |
| static struct shared_resource *vdd2_resp; |
| static struct device dummy_mpu_dev; |
| static struct device dummy_dsp_dev; |
| static struct device vdd2_dev; |
| static int vdd1_lock; |
| static int vdd2_lock; |
| static struct clk *dpll1_clk, *dpll2_clk, *dpll3_clk; |
| static int curr_vdd1_opp; |
| static int curr_vdd2_opp; |
| static DEFINE_MUTEX(dvfs_mutex); |
| |
| static unsigned short get_opp(struct omap_opp *opp_freq_table, |
| unsigned long freq) |
| { |
| struct omap_opp *prcm_config; |
| prcm_config = opp_freq_table; |
| |
| if (prcm_config->rate <= freq) |
| return prcm_config->opp_id; /* Return the Highest OPP */ |
| for (; prcm_config->rate; prcm_config--) |
| if (prcm_config->rate < freq) |
| return (prcm_config+1)->opp_id; |
| else if (prcm_config->rate == freq) |
| return prcm_config->opp_id; |
| /* Return the least OPP */ |
| return (prcm_config+1)->opp_id; |
| } |
| |
| /** |
| * init_opp - Initialize the OPP resource |
| */ |
| void init_opp(struct shared_resource *resp) |
| { |
| struct clk *l3_clk; |
| resp->no_of_users = 0; |
| |
| if (!mpu_opps || !dsp_opps || !l3_opps) |
| return; |
| |
| /* Initialize the current level of the OPP resource |
| * to the opp set by u-boot. |
| */ |
| if (strcmp(resp->name, "vdd1_opp") == 0) { |
| vdd1_resp = resp; |
| dpll1_clk = clk_get(NULL, "dpll1_ck"); |
| dpll2_clk = clk_get(NULL, "dpll2_ck"); |
| resp->curr_level = get_opp(mpu_opps + MAX_VDD1_OPP, |
| dpll1_clk->rate); |
| curr_vdd1_opp = resp->curr_level; |
| } else if (strcmp(resp->name, "vdd2_opp") == 0) { |
| vdd2_resp = resp; |
| dpll3_clk = clk_get(NULL, "dpll3_m2_ck"); |
| l3_clk = clk_get(NULL, "l3_ick"); |
| resp->curr_level = get_opp(l3_opps + MAX_VDD2_OPP, |
| l3_clk->rate); |
| curr_vdd2_opp = resp->curr_level; |
| } |
| return; |
| } |
| |
| int resource_access_opp_lock(int res, int delta) |
| { |
| if (res == VDD1_OPP) { |
| vdd1_lock += delta; |
| return vdd1_lock; |
| } else if (res == VDD2_OPP) { |
| vdd2_lock += delta; |
| return vdd2_lock; |
| } |
| return -EINVAL; |
| } |
| |
| #ifndef CONFIG_CPU_FREQ |
| static unsigned long compute_lpj(unsigned long ref, u_int div, u_int mult) |
| { |
| unsigned long new_jiffy_l, new_jiffy_h; |
| |
| /* |
| * Recalculate loops_per_jiffy. We do it this way to |
| * avoid math overflow on 32-bit machines. Maybe we |
| * should make this architecture dependent? If you have |
| * a better way of doing this, please replace! |
| * |
| * new = old * mult / div |
| */ |
| new_jiffy_h = ref / div; |
| new_jiffy_l = (ref % div) / 100; |
| new_jiffy_h *= mult; |
| new_jiffy_l = new_jiffy_l * mult / div; |
| |
| return new_jiffy_h + new_jiffy_l * 100; |
| } |
| #endif |
| |
| static int program_opp_freq(int res, int target_level, int current_level) |
| { |
| int ret = 0, l3_div; |
| int *curr_opp; |
| |
| lock_scratchpad_sem(); |
| if (res == VDD1_OPP) { |
| curr_opp = &curr_vdd1_opp; |
| clk_set_rate(dpll1_clk, mpu_opps[target_level].rate); |
| clk_set_rate(dpll2_clk, dsp_opps[target_level].rate); |
| #ifndef CONFIG_CPU_FREQ |
| /*Update loops_per_jiffy if processor speed is being changed*/ |
| loops_per_jiffy = compute_lpj(loops_per_jiffy, |
| mpu_opps[current_level].rate/1000, |
| mpu_opps[target_level].rate/1000); |
| #endif |
| } else { |
| curr_opp = &curr_vdd2_opp; |
| l3_div = cm_read_mod_reg(CORE_MOD, CM_CLKSEL) & |
| OMAP3430_CLKSEL_L3_MASK; |
| ret = clk_set_rate(dpll3_clk, |
| l3_opps[target_level].rate * l3_div); |
| } |
| if (ret) { |
| unlock_scratchpad_sem(); |
| return current_level; |
| } |
| #ifdef CONFIG_PM |
| omap3_save_scratchpad_contents(); |
| #endif |
| unlock_scratchpad_sem(); |
| |
| *curr_opp = target_level; |
| return target_level; |
| } |
| |
| static int program_opp(int res, struct omap_opp *opp, int target_level, |
| int current_level) |
| { |
| int i, ret = 0, raise; |
| #ifdef CONFIG_OMAP_SMARTREFLEX |
| unsigned long t_opp, c_opp; |
| |
| t_opp = ID_VDD(res) | ID_OPP_NO(opp[target_level].opp_id); |
| c_opp = ID_VDD(res) | ID_OPP_NO(opp[current_level].opp_id); |
| #endif |
| if (target_level > current_level) |
| raise = 1; |
| else |
| raise = 0; |
| |
| for (i = 0; i < 2; i++) { |
| if (i == raise) |
| ret = program_opp_freq(res, target_level, |
| current_level); |
| #ifdef CONFIG_OMAP_SMARTREFLEX |
| else |
| sr_voltagescale_vcbypass(t_opp, c_opp, |
| opp[target_level].vsel, |
| opp[current_level].vsel); |
| #endif |
| } |
| |
| return ret; |
| } |
| |
| int resource_set_opp_level(int res, u32 target_level, int flags) |
| { |
| unsigned long mpu_freq, mpu_old_freq; |
| #ifdef CONFIG_CPU_FREQ |
| struct cpufreq_freqs freqs_notify; |
| #endif |
| struct shared_resource *resp; |
| |
| if (res == VDD1_OPP) |
| resp = vdd1_resp; |
| else if (res == VDD2_OPP) |
| resp = vdd2_resp; |
| else |
| return 0; |
| |
| if (resp->curr_level == target_level) |
| return 0; |
| |
| if (!mpu_opps || !dsp_opps || !l3_opps) |
| return 0; |
| |
| mutex_lock(&dvfs_mutex); |
| |
| if (res == VDD1_OPP) { |
| if (flags != OPP_IGNORE_LOCK && vdd1_lock) { |
| mutex_unlock(&dvfs_mutex); |
| return 0; |
| } |
| mpu_old_freq = mpu_opps[resp->curr_level].rate; |
| mpu_freq = mpu_opps[target_level].rate; |
| |
| #ifdef CONFIG_CPU_FREQ |
| freqs_notify.old = mpu_old_freq/1000; |
| freqs_notify.new = mpu_freq/1000; |
| freqs_notify.cpu = 0; |
| /* Send pre notification to CPUFreq */ |
| cpufreq_notify_transition(&freqs_notify, CPUFREQ_PRECHANGE); |
| #endif |
| resp->curr_level = program_opp(res, mpu_opps, target_level, |
| resp->curr_level); |
| #ifdef CONFIG_CPU_FREQ |
| /* Send a post notification to CPUFreq */ |
| cpufreq_notify_transition(&freqs_notify, CPUFREQ_POSTCHANGE); |
| #endif |
| } else { |
| if (!(flags & OPP_IGNORE_LOCK) && vdd2_lock) { |
| mutex_unlock(&dvfs_mutex); |
| return 0; |
| } |
| resp->curr_level = program_opp(res, l3_opps, target_level, |
| resp->curr_level); |
| } |
| mutex_unlock(&dvfs_mutex); |
| return 0; |
| } |
| |
| int set_opp(struct shared_resource *resp, u32 target_level) |
| { |
| unsigned long tput; |
| unsigned long req_l3_freq; |
| int ind; |
| |
| if (resp == vdd1_resp) { |
| if (target_level < 3) |
| resource_release("vdd2_opp", &vdd2_dev); |
| |
| resource_set_opp_level(VDD1_OPP, target_level, 0); |
| /* |
| * For VDD1 OPP3 and above, make sure the interconnect |
| * is at 100Mhz or above. |
| * throughput in KiB/s for 100 Mhz = 100 * 1000 * 4. |
| */ |
| if (target_level >= 3) |
| resource_request("vdd2_opp", &vdd2_dev, 400000); |
| |
| } else if (resp == vdd2_resp) { |
| tput = target_level; |
| |
| /* Convert the tput in KiB/s to Bus frequency in MHz */ |
| req_l3_freq = (tput * 1000)/4; |
| |
| for (ind = 2; ind <= MAX_VDD2_OPP; ind++) |
| if ((l3_opps + ind)->rate >= req_l3_freq) { |
| target_level = ind; |
| break; |
| } |
| |
| /* Set the highest OPP possible */ |
| if (ind > MAX_VDD2_OPP) |
| target_level = ind-1; |
| resource_set_opp_level(VDD2_OPP, target_level, 0); |
| } |
| return 0; |
| } |
| |
| /** |
| * validate_opp - Validates if valid VDD1 OPP's are passed as the |
| * target_level. |
| * VDD2 OPP levels are passed as L3 throughput, which are then mapped |
| * to an appropriate OPP. |
| */ |
| int validate_opp(struct shared_resource *resp, u32 target_level) |
| { |
| return 0; |
| } |
| |
| /** |
| * init_freq - Initialize the frequency resource. |
| */ |
| void init_freq(struct shared_resource *resp) |
| { |
| char *linked_res_name; |
| resp->no_of_users = 0; |
| |
| if (!mpu_opps || !dsp_opps) |
| return; |
| |
| linked_res_name = (char *)resp->resource_data; |
| /* Initialize the current level of the Freq resource |
| * to the frequency set by u-boot. |
| */ |
| if (strcmp(resp->name, "mpu_freq") == 0) |
| /* MPU freq in Mhz */ |
| resp->curr_level = mpu_opps[curr_vdd1_opp].rate; |
| else if (strcmp(resp->name, "dsp_freq") == 0) |
| /* DSP freq in Mhz */ |
| resp->curr_level = dsp_opps[curr_vdd1_opp].rate; |
| return; |
| } |
| |
| int set_freq(struct shared_resource *resp, u32 target_level) |
| { |
| unsigned int vdd1_opp; |
| |
| if (!mpu_opps || !dsp_opps) |
| return 0; |
| |
| if (strcmp(resp->name, "mpu_freq") == 0) { |
| vdd1_opp = get_opp(mpu_opps + MAX_VDD1_OPP, target_level); |
| resource_request("vdd1_opp", &dummy_mpu_dev, vdd1_opp); |
| } else if (strcmp(resp->name, "dsp_freq") == 0) { |
| vdd1_opp = get_opp(dsp_opps + MAX_VDD1_OPP, target_level); |
| resource_request("vdd1_opp", &dummy_dsp_dev, vdd1_opp); |
| } |
| resp->curr_level = target_level; |
| return 0; |
| } |
| |
| int validate_freq(struct shared_resource *resp, u32 target_level) |
| { |
| return 0; |
| } |