diff options
author | Justin Waters <justin.waters@timesys.com> | 2012-03-21 13:28:20 -0400 |
---|---|---|
committer | Justin Waters <justin.waters@timesys.com> | 2012-03-21 13:28:20 -0400 |
commit | d0183eb2433e3332c2720637238b18b1fdff7946 (patch) | |
tree | 36be0be2c433789656750da0ca5991250fc7d3e7 /arch/arm/plat-stmp3xxx/cpufreq.c | |
parent | 74fca6a42863ffacaf7ba6f1936a9f228950f657 (diff) |
Add support for the i.MX28 EVK
This patch was originally put together in January 2011 by Roshni.
Diffstat (limited to 'arch/arm/plat-stmp3xxx/cpufreq.c')
-rw-r--r-- | arch/arm/plat-stmp3xxx/cpufreq.c | 573 |
1 files changed, 573 insertions, 0 deletions
diff --git a/arch/arm/plat-stmp3xxx/cpufreq.c b/arch/arm/plat-stmp3xxx/cpufreq.c new file mode 100644 index 000000000000..d47e061523a3 --- /dev/null +++ b/arch/arm/plat-stmp3xxx/cpufreq.c @@ -0,0 +1,573 @@ +/* + * CPU frequency scaling for Freescale STMP37XX/STMP378X + * + * Author: Vitaly Wool <vital@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/cpufreq.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/notifier.h> + +#include <mach/hardware.h> +#include <linux/io.h> +#include <asm/system.h> +#include <mach/regulator.h> +#include <mach/power.h> +#include <mach/regs-digctl.h> +#include <mach/regs-clkctrl.h> +#include <mach/platform.h> +#include "clock.h" + +#define VERY_HI_RATE 2000000000 +#define CLKCTRL_PLL_PWD_BIT 16 +#define CLKCTRL_PLL_BYPASS 0x1ff +#define CLKCTRL_HBUS_AUTO_SLOW_MODE_BIT 20 +#define LCD_ON_CPU_FREQ_KHZ 261820 + +static struct profile { + int cpu; + int ahb; + int emi; + int ss; + int vddd; + int vddd_bo; + int cur; + int vddio; + int vdda; + int pll_off; +} profiles[] = { + { 454740, 151580, 130910, 0, 1550000, + 1450000, 355000, 3300000, 1750000, 0 }, + { 392730, 130910, 130910, 0, 1475000, + 1375000, 225000, 3300000, 1750000, 0 }, + { 360000, 120000, 120000, 0, 1375000, + 1275000, 200000, 3300000, 1750000, 0 }, + { 261820, 130910, 130910, 0, 1275000, + 1175000, 173000, 3300000, 1750000, 0 }, +#ifdef CONFIG_STMP378X_RAM_MDDR + { 64000, 64000, 48000, 3, 1050000, + 975000, 150000, 3300000, 1750000, 0 }, + { 24000, 24000, 24000, 3, 1050000, + 975000, 150000, 3075000, 1725000, 1 }, +#else + { 64000, 64000, 96000, 3, 1050000, + 975000, 150000, 3300000, 1750000, 0 }, +#endif +}; + +static u32 clkseq_setting; +static struct regulator *cpu_regulator; +static struct clk *cpu_clk; +static struct clk *ahb_clk; +static struct clk *emi_clk; +static struct clk *usb_clk; +static struct clk *lcdif_clk; +static struct regulator *vddd; +static struct regulator *vdddbo; +static struct regulator *vddio; +static struct regulator *vdda; +static struct cpufreq_frequency_table imx_freq_table[7]; +int cpu_freq_khz_min; +int cpu_freq_khz_max; +int cpufreq_trig_needed; +int cur_freq_table_size; +int lcd_on_freq_table_size; +extern int clk_get_usage(struct clk *clk); + +static void hbus_auto_slow_mode_enable(void) +{ + __raw_writel(CLKCTRL_HBUS_AUTO_SLOW_MODE_BIT, + REGS_CLKCTRL_BASE + HW_CLKCTRL_HBUS_SET); +} + +static void hbus_auto_slow_mode_disable(void) +{ + __raw_writel(CLKCTRL_HBUS_AUTO_SLOW_MODE_BIT, + REGS_CLKCTRL_BASE + HW_CLKCTRL_HBUS_CLR); +} + +int low_freq_used(void) +{ + if ((clk_get_usage(usb_clk) == 0) + && (clk_get_usage(lcdif_clk) == 0)) + return 1; + else + return 0; + } + +static int set_freq_table(struct cpufreq_policy *policy, int end_index) +{ + int ret = 0; + int i; + + cpu_freq_khz_min = profiles[0].cpu; + cpu_freq_khz_max = profiles[0].cpu; + for (i = 0; i < end_index; i++) { + imx_freq_table[end_index - 1 - i].index = end_index - i; + imx_freq_table[end_index - 1 - i].frequency = + profiles[i].cpu; + + if ((profiles[i].cpu) < cpu_freq_khz_min) + cpu_freq_khz_min = profiles[i].cpu; + + if ((profiles[i].cpu) > cpu_freq_khz_max) + cpu_freq_khz_max = profiles[i].cpu; + } + + imx_freq_table[i].index = 0; + imx_freq_table[i].frequency = CPUFREQ_TABLE_END; + + policy->cur = clk_get_rate(cpu_clk); + policy->governor = CPUFREQ_DEFAULT_GOVERNOR; + policy->min = policy->cpuinfo.min_freq = cpu_freq_khz_min; + policy->max = policy->cpuinfo.max_freq = cpu_freq_khz_max; + + /* Manual states, that PLL stabilizes in two CLK32 periods */ + policy->cpuinfo.transition_latency = 1000; + + ret = cpufreq_frequency_table_cpuinfo(policy, imx_freq_table); + + if (ret < 0) { + printk(KERN_ERR "%s: failed to register i.MXC CPUfreq\n", + __func__); + return ret; + } + + cpufreq_frequency_table_get_attr(imx_freq_table, policy->cpu); + + return ret; +} + +static int set_op(unsigned int target_freq) +{ + struct cpufreq_freqs freqs; + int ret = 0, i; + + freqs.old = clk_get_rate(cpu_clk); + freqs.cpu = 0; + +/* work around usb problem when in updater firmare mode*/ +#ifdef CONFIG_FSL_UTP + return 0; +#endif + for (i = cur_freq_table_size - 1; i > 0; i--) { + if (profiles[i].cpu <= target_freq && + target_freq < profiles[i - 1].cpu) { + freqs.new = profiles[i].cpu; + break; + } + + if (!vddd && profiles[i].cpu > freqs.old) { + /* can't safely set more than now */ + freqs.new = profiles[i + 1].cpu; + break; + } + } + + if (i == 0) + freqs.new = profiles[i].cpu; + + if (freqs.old == freqs.new) { + if (regulator_get_voltage(vddd) == profiles[i].vddd) + return 0; + } + + if (freqs.old == 24000 && freqs.new > 24000) { + /* turn pll on */ + __raw_writel(CLKCTRL_PLL_PWD_BIT, REGS_CLKCTRL_BASE + + HW_CLKCTRL_PLLCTRL0_SET); + udelay(10); + } else if (freqs.old > 24000 && freqs.new == 24000) + clkseq_setting = __raw_readl(REGS_CLKCTRL_BASE + + HW_CLKCTRL_CLKSEQ); + + if (cpu_regulator && (freqs.old < freqs.new)) { + ret = regulator_set_current_limit(cpu_regulator, + profiles[i].cur, profiles[i].cur); + if (ret) + return ret; + } + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + if (freqs.old > freqs.new) { + int ss = profiles[i].ss; + clk_set_rate(cpu_clk, profiles[i].cpu); + clk_set_rate(ahb_clk, profiles[i].ahb); + clk_set_rate(emi_clk, profiles[i].emi); + __raw_writel(BF(ss, DIGCTL_ARMCACHE_VALID_SS) | + BF(ss, DIGCTL_ARMCACHE_DRTY_SS) | + BF(ss, DIGCTL_ARMCACHE_CACHE_SS) | + BF(ss, DIGCTL_ARMCACHE_DTAG_SS) | + BF(ss, DIGCTL_ARMCACHE_ITAG_SS), + REGS_DIGCTL_BASE + HW_DIGCTL_ARMCACHE); + if (vddd && vdddbo && vddio && vdda) { + ret = regulator_set_voltage(vddd, + profiles[i].vddd, + profiles[i].vddd); + if (ret) + ret = regulator_set_voltage(vddd, + profiles[i].vddd, + profiles[i].vddd); + regulator_set_voltage(vdddbo, + profiles[i].vddd_bo, + profiles[i].vddd_bo); + + ret = regulator_set_voltage(vddio, + profiles[i].vddio, + profiles[i].vddio); + if (ret) + ret = regulator_set_voltage(vddio, + profiles[i].vddio, + profiles[i].vddio); + ret = regulator_set_voltage(vdda, + profiles[i].vdda, + profiles[i].vdda); + if (ret) + ret = regulator_set_voltage(vdda, + profiles[i].vdda, + profiles[i].vdda); + } + } else { + int ss = profiles[i].ss; + if (vddd && vdddbo && vddio && vdda) { + ret = regulator_set_voltage(vddd, + profiles[i].vddd, + profiles[i].vddd); + if (ret) + ret = regulator_set_voltage(vddd, + profiles[i].vddd, + profiles[i].vddd); + regulator_set_voltage(vdddbo, + profiles[i].vddd_bo, + profiles[i].vddd_bo); + ret = regulator_set_voltage(vddio, + profiles[i].vddio, + profiles[i].vddio); + if (ret) + ret = regulator_set_voltage(vddio, + profiles[i].vddio, + profiles[i].vddio); + ret = regulator_set_voltage(vdda, + profiles[i].vdda, + profiles[i].vdda); + if (ret) + ret = regulator_set_voltage(vdda, + profiles[i].vdda, + profiles[i].vdda); + } + __raw_writel(BF(ss, DIGCTL_ARMCACHE_VALID_SS) | + BF(ss, DIGCTL_ARMCACHE_DRTY_SS) | + BF(ss, DIGCTL_ARMCACHE_CACHE_SS) | + BF(ss, DIGCTL_ARMCACHE_DTAG_SS) | + BF(ss, DIGCTL_ARMCACHE_ITAG_SS), + REGS_DIGCTL_BASE + HW_DIGCTL_ARMCACHE); + clk_set_rate(cpu_clk, profiles[i].cpu); + clk_set_rate(ahb_clk, profiles[i].ahb); + clk_set_rate(emi_clk, profiles[i].emi); + } + udelay(100); + + if (freqs.old > 24000 && freqs.new == 24000) { + /* turn pll off */ + __raw_writel(CLKCTRL_PLL_PWD_BIT, REGS_CLKCTRL_BASE + + HW_CLKCTRL_PLLCTRL0_CLR); + __raw_writel(CLKCTRL_PLL_BYPASS, REGS_CLKCTRL_BASE + + HW_CLKCTRL_CLKSEQ); + } else if (freqs.old == 24000 && freqs.new > 24000) + __raw_writel(clkseq_setting, REGS_CLKCTRL_BASE + + HW_CLKCTRL_CLKSEQ); + + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + if (cpu_regulator && (freqs.old > freqs.new)) /* will not fail */ + regulator_set_current_limit(cpu_regulator, + profiles[i].cur, + profiles[i].cur); + + return ret; +} + +static int calc_frequency_khz(int target, unsigned int relation) +{ + int i; + + if (target == clk_get_rate(cpu_clk)) + return target; + + if (relation == CPUFREQ_RELATION_H) { + for (i = cur_freq_table_size - 1; i >= 0; i--) { + if (imx_freq_table[i].frequency <= target) + return imx_freq_table[i].frequency; + } + } else if (relation == CPUFREQ_RELATION_L) { + for (i = 0; i < cur_freq_table_size; i++) { + if (imx_freq_table[i].frequency >= target) + return imx_freq_table[i].frequency; + } +} + + printk(KERN_ERR "Error: No valid cpufreq relation\n"); + return cpu_freq_khz_max; +} + +static int stmp3xxx_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + int freq_KHz; + struct cpufreq_freqs freqs; + int low_freq_bus_ready = 0; + + if (cpufreq_trig_needed == 1) { + /* Set the current working point. */ + cpufreq_trig_needed = 0; + target_freq = clk_get_rate(cpu_clk); + freq_KHz = calc_frequency_khz(target_freq, relation); + + freqs.old = target_freq; + freqs.new = freq_KHz; + freqs.cpu = 0; + freqs.flags = 0; + + low_freq_bus_ready = low_freq_used(); + if (low_freq_bus_ready) { + cur_freq_table_size = ARRAY_SIZE(profiles); + hbus_auto_slow_mode_enable(); + } else { + cur_freq_table_size = lcd_on_freq_table_size; + hbus_auto_slow_mode_disable(); + } + + set_freq_table(policy, cur_freq_table_size); + + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + + /* + * Some governors do not respects CPU and policy lower limits + * which leads to bad things (division by zero etc), ensure + * that such things do not happen. + */ + if (target_freq < policy->cpuinfo.min_freq) + target_freq = policy->cpuinfo.min_freq; + + if (target_freq < policy->min) + target_freq = policy->min; + + freq_KHz = calc_frequency_khz(target_freq, relation); + return set_op(freq_KHz); + } + +static unsigned int stmp3xxx_getspeed(unsigned int cpu) +{ + struct cpufreq_freqs freqs; + int freq_KHz; + unsigned int target_freq; + + if (cpu) + return 0; + + if (cpufreq_trig_needed == 1) { + target_freq = clk_get_rate(cpu_clk); + freq_KHz = calc_frequency_khz(target_freq, CPUFREQ_RELATION_L); + + freqs.old = target_freq; + freqs.new = freq_KHz; + freqs.cpu = 0; + freqs.flags = 0; + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + } + + return clk_get_rate(cpu_clk); +} + + +static int stmp3xxx_verify_speed(struct cpufreq_policy *policy) +{ + if (policy->cpu != 0) + return -EINVAL; + + return cpufreq_frequency_table_verify(policy, imx_freq_table); +} + +static int __init stmp3xxx_cpu_init(struct cpufreq_policy *policy) +{ + int ret = 0; + int i; + + cpu_clk = clk_get(NULL, "cpu"); + if (IS_ERR(cpu_clk)) { + ret = PTR_ERR(cpu_clk); + goto out_cpu; + } + + ahb_clk = clk_get(NULL, "hclk"); + if (IS_ERR(ahb_clk)) { + ret = PTR_ERR(ahb_clk); + goto out_ahb; + } + + emi_clk = clk_get(NULL, "emi"); + if (IS_ERR(emi_clk)) { + ret = PTR_ERR(emi_clk); + goto out_emi; + } + + usb_clk = clk_get(NULL, "usb"); + if (IS_ERR(usb_clk)) { + ret = PTR_ERR(usb_clk); + goto out_usb; + } + + lcdif_clk = clk_get(NULL, "lcdif"); + if (IS_ERR(lcdif_clk)) { + ret = PTR_ERR(lcdif_clk); + goto out_lcd; + } + + if (policy->cpu != 0) + return -EINVAL; + + cpu_regulator = regulator_get(NULL, "cpufreq-1"); + if (IS_ERR(cpu_regulator)) { + printk(KERN_ERR "%s: failed to get CPU regulator\n", __func__); + cpu_regulator = NULL; + ret = PTR_ERR(cpu_regulator); + goto out_cur; + } + + vddd = regulator_get(NULL, "vddd"); + if (IS_ERR(vddd)) { + printk(KERN_ERR "%s: failed to get vddd regulator\n", __func__); + vddd = NULL; + ret = PTR_ERR(vddd); + goto out_cur; + } + + vdddbo = regulator_get(NULL, "vddd_bo"); + if (IS_ERR(vdddbo)) { + vdddbo = NULL; + pr_warning("unable to get vdddbo"); + ret = PTR_ERR(vdddbo); + goto out_cur; + } + + vddio = regulator_get(NULL, "vddio"); + if (IS_ERR(vddio)) { + vddio = NULL; + pr_warning("unable to get vddio"); + ret = PTR_ERR(vddio); + goto out_cur; + } + vdda = regulator_get(NULL, "vdda"); + if (IS_ERR(vdda)) { + vdda = NULL; + pr_warning("unable to get vdda"); + ret = PTR_ERR(vdda); + goto out_cur; + } + + for (i = 0; i < ARRAY_SIZE(profiles); i++) { + if ((profiles[i].cpu) == LCD_ON_CPU_FREQ_KHZ) { + lcd_on_freq_table_size = i + 1; + break; + } + } + + if (i == ARRAY_SIZE(profiles)) { + pr_warning("unable to find frequency for LCD on"); + printk(KERN_ERR "lcd_on_freq_table_size=%d\n", + lcd_on_freq_table_size); + goto out_cur; + } + + /* Set the current working point. */ + set_freq_table(policy, lcd_on_freq_table_size); + cpufreq_trig_needed = 0; + cur_freq_table_size = lcd_on_freq_table_size; + return 0; +out_cur: + if (cpu_regulator) + regulator_put(cpu_regulator); + if (vddd) + regulator_put(vddd); + if (vddio) + regulator_put(vddio); + if (vdda) + regulator_put(vdda); + + clk_put(lcdif_clk); +out_lcd: + clk_put(usb_clk); +out_usb: + clk_put(emi_clk); +out_emi: + clk_put(ahb_clk); +out_ahb: + clk_put(cpu_clk); +out_cpu: + return ret; +} + +static int stmp3xxx_cpu_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + + /* Reset CPU to 392MHz */ + set_op(profiles[1].cpu); + + clk_put(cpu_clk); + regulator_put(cpu_regulator); + return 0; +} + +static struct cpufreq_driver stmp3xxx_driver = { + .flags = CPUFREQ_STICKY, + .verify = stmp3xxx_verify_speed, + .target = stmp3xxx_target, + .get = stmp3xxx_getspeed, + .init = stmp3xxx_cpu_init, + .exit = stmp3xxx_cpu_exit, + .name = "stmp3xxx", +}; + +static int __devinit stmp3xxx_cpufreq_init(void) +{ + return cpufreq_register_driver(&stmp3xxx_driver); +} + +static void stmp3xxx_cpufreq_exit(void) +{ + cpufreq_unregister_driver(&stmp3xxx_driver); +} + +module_init(stmp3xxx_cpufreq_init); +module_exit(stmp3xxx_cpufreq_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("CPUfreq driver for i.MX"); +MODULE_LICENSE("GPL"); + |