diff options
author | Colin Cross <ccross@android.com> | 2011-06-01 20:12:34 -0700 |
---|---|---|
committer | Colin Cross <ccross@android.com> | 2011-06-01 20:12:34 -0700 |
commit | 1126b835e610af022d9d2fd90f3ce423fbb5c1f0 (patch) | |
tree | 74ba3acb6fbcfd7207c947879f73bbbc683339d4 | |
parent | dbe76d21ffd81cf09febfcc164b9cbec647f9ede (diff) | |
parent | 01f30f832bcd4cb680fa754fdcf22c3b4c2c25be (diff) | |
download | omap-sandbox/ccross/linux-omap-2.6.39-pm.tar.gz |
Merge branch 'linux-omap-2.6.39-integration-power' of git://github.com/nmenon/linux-omap-ti-pm into linux-omap-2.6.39-pmsandbox/ccross/linux-omap-2.6.39-pm
112 files changed, 9405 insertions, 2061 deletions
diff --git a/Documentation/cpu-freq/governors.txt b/Documentation/cpu-freq/governors.txt index e74d0a2eb1cf..c2e3d3d22ddd 100644 --- a/Documentation/cpu-freq/governors.txt +++ b/Documentation/cpu-freq/governors.txt @@ -193,6 +193,34 @@ governor but for the opposite direction. For example when set to its default value of '20' it means that if the CPU usage needs to be below 20% between samples to have the frequency decreased. + +2.6 Hotplug +----------- + +The CPUfreq governor "hotplug" operates similary to "ondemand" and +"conservative". It's decisions are based primarily on CPU load. Like +"ondemand" the "hotplug" governor will ramp up to the highest frequency +once the run-time tunable "up_threshold" parameter is crossed. Like +"conservative", the "hotplug" governor exports a "down_threshold" +parameter that is also tunable at run-time. When the "down_threshold" +is crossed the CPU transitions to the next lowest frequency in the +CPUfreq frequency table instead of decrementing the frequency based on a +percentage of maximum load. + +The main reason "hotplug" governor exists is for architectures requiring +that only the master CPU be online in order to hit low-power states +(C-states). OMAP4 is one such example of this. The "hotplug" governor +is also helpful in reducing thermal output in devices with tight thermal +constraints. + +Auxillary CPUs are onlined/offline based on CPU load, but the decision +to do so is made after averaging several sampling windows. This is to +reduce CPU hotplug "thrashing", which can be caused by normal system +entropy and leads to lots of spurious plug-in and plug-out transitions. +The number of sampling periods averaged together is tunable via the +"hotplug_in_sampling_periods" and "hotplug_out_sampling_periods" +run-time tunable parameters. + 3. The Governor Interface in the CPUfreq Core ============================================= diff --git a/Documentation/power/opp.txt b/Documentation/power/opp.txt index 5ae70a12c1e2..3035d00757ad 100644 --- a/Documentation/power/opp.txt +++ b/Documentation/power/opp.txt @@ -321,6 +321,8 @@ opp_init_cpufreq_table - cpufreq framework typically is initialized with addition to CONFIG_PM as power management feature is required to dynamically scale voltage and frequency in a system. +opp_free_cpufreq_table - Free up the table allocated by opp_init_cpufreq_table + 7. Data Structures ================== Typically an SoC contains multiple voltage domains which are variable. Each diff --git a/arch/arm/configs/omap2plus_defconfig b/arch/arm/configs/omap2plus_defconfig index 076db52ff672..19f199172642 100644 --- a/arch/arm/configs/omap2plus_defconfig +++ b/arch/arm/configs/omap2plus_defconfig @@ -24,6 +24,7 @@ CONFIG_ARCH_OMAP=y CONFIG_ARCH_OMAP2=y CONFIG_ARCH_OMAP3=y CONFIG_ARCH_OMAP4=y +CONFIG_OMAP_SMARTREFLEX=y CONFIG_OMAP_RESET_CLOCKS=y CONFIG_OMAP_MUX_DEBUG=y CONFIG_OMAP_32K_TIMER=y @@ -66,6 +67,14 @@ CONFIG_ZBOOT_ROM_TEXT=0x0 CONFIG_ZBOOT_ROM_BSS=0x0 CONFIG_CMDLINE="root=/dev/mmcblk0p2 rootwait console=ttyO2,115200" CONFIG_KEXEC=y +CONFIG_CPU_FREQ=y +CONFIG_CPU_FREQ_DEFAULT_GOV_USERSPACE=y +CONFIG_CPU_FREQ_GOV_PERFORMANCE=y +CONFIG_CPU_FREQ_GOV_POWERSAVE=y +CONFIG_CPU_FREQ_GOV_ONDEMAND=y +CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y +CONFIG_CPU_FREQ_GOV_HOTPLUG=y +CONFIG_CPU_IDLE=y CONFIG_FPE_NWFPE=y CONFIG_VFP=y CONFIG_NEON=y diff --git a/arch/arm/include/asm/hardware/cache-l2x0.h b/arch/arm/include/asm/hardware/cache-l2x0.h index 16bd48031583..8dee14a2682a 100644 --- a/arch/arm/include/asm/hardware/cache-l2x0.h +++ b/arch/arm/include/asm/hardware/cache-l2x0.h @@ -45,23 +45,25 @@ #define L2X0_CLEAN_INV_LINE_PA 0x7F0 #define L2X0_CLEAN_INV_LINE_IDX 0x7F8 #define L2X0_CLEAN_INV_WAY 0x7FC -#define L2X0_LOCKDOWN_WAY_D 0x900 -#define L2X0_LOCKDOWN_WAY_I 0x904 +#define L2X0_LOCKDOWN_WAY_D0 0x900 +#define L2X0_LOCKDOWN_WAY_D1 0x908 +#define L2X0_LOCKDOWN_WAY_I0 0x904 +#define L2X0_LOCKDOWN_WAY_I1 0x90C #define L2X0_TEST_OPERATION 0xF00 #define L2X0_LINE_DATA 0xF10 #define L2X0_LINE_TAG 0xF30 #define L2X0_DEBUG_CTRL 0xF40 #define L2X0_PREFETCH_CTRL 0xF60 #define L2X0_POWER_CTRL 0xF80 -#define L2X0_DYNAMIC_CLK_GATING_EN (1 << 1) -#define L2X0_STNDBY_MODE_EN (1 << 0) +#define L2X0_DYNAMIC_CLK_GATING_EN (1 << 1) +#define L2X0_STNDBY_MODE_EN (1 << 0) /* Registers shifts and masks */ #define L2X0_CACHE_ID_PART_MASK (0xf << 6) #define L2X0_CACHE_ID_PART_L210 (1 << 6) #define L2X0_CACHE_ID_PART_L310 (3 << 6) -#define L2X0_AUX_CTRL_MASK 0xc0000fff +#define L2X0_AUX_CTRL_MASK 0xd0000fff #define L2X0_AUX_CTRL_ASSOCIATIVITY_SHIFT 16 #define L2X0_AUX_CTRL_WAY_SIZE_SHIFT 17 #define L2X0_AUX_CTRL_WAY_SIZE_MASK (0x3 << 17) @@ -72,6 +74,10 @@ #define L2X0_AUX_CTRL_INSTR_PREFETCH_SHIFT 29 #define L2X0_AUX_CTRL_EARLY_BRESP_SHIFT 30 +#define L2X0_PREFETCH_DATA_PREFETCH_SHIFT 28 +#define L2X0_PREFETCH_INTSTR_PREFETCH_SHIFT 29 +#define L2X0_PREFETCH_DOUBLE_LINEFILL_SHIFT 30 + #ifndef __ASSEMBLY__ extern void __init l2x0_init(void __iomem *base, __u32 aux_val, __u32 aux_mask); #endif diff --git a/arch/arm/kernel/smp.c b/arch/arm/kernel/smp.c index f29b8a29b174..a9db4bd521c2 100644 --- a/arch/arm/kernel/smp.c +++ b/arch/arm/kernel/smp.c @@ -277,6 +277,7 @@ asmlinkage void __cpuinit secondary_start_kernel(void) { struct mm_struct *mm = &init_mm; unsigned int cpu = smp_processor_id(); + static bool booted; printk("CPU%u: Booted secondary processor\n", cpu); @@ -312,7 +313,9 @@ asmlinkage void __cpuinit secondary_start_kernel(void) */ percpu_timer_setup(); - calibrate_delay(); + if (!booted) + calibrate_delay(); + booted = true; smp_store_cpu_info(cpu); diff --git a/arch/arm/kernel/smp_twd.c b/arch/arm/kernel/smp_twd.c index 60636f499cb3..dbf58d43c8c5 100644 --- a/arch/arm/kernel/smp_twd.c +++ b/arch/arm/kernel/smp_twd.c @@ -10,21 +10,29 @@ */ #include <linux/init.h> #include <linux/kernel.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/err.h> #include <linux/smp.h> #include <linux/jiffies.h> #include <linux/clockchips.h> #include <linux/irq.h> #include <linux/io.h> +#include <linux/percpu.h> #include <asm/smp_twd.h> #include <asm/hardware/gic.h> +#define TWD_MIN_RANGE 4 + /* set up by the platform code */ void __iomem *twd_base; +static struct clk *twd_clk; static unsigned long twd_timer_rate; +static DEFINE_PER_CPU(struct clock_event_device *, twd_ce); static void twd_set_mode(enum clock_event_mode mode, struct clock_event_device *clk) @@ -80,6 +88,49 @@ int twd_timer_ack(void) return 0; } +/* + * Updates clockevent frequency when the cpu frequency changes. + * Called on the cpu that is changing frequency with interrupts disabled. + */ +static void twd_update_frequency(void *data) +{ + twd_timer_rate = clk_get_rate(twd_clk); + + clockevents_reconfigure(__get_cpu_var(twd_ce), twd_timer_rate, + TWD_MIN_RANGE); +} + +static int twd_cpufreq_transition(struct notifier_block *nb, + unsigned long state, void *data) +{ + struct cpufreq_freqs *freqs = data; + + /* + * The twd clock events must be reprogrammed to account for the new + * frequency. The timer is local to a cpu, so cross-call to the + * changing cpu. + */ + if (state == CPUFREQ_POSTCHANGE || state == CPUFREQ_RESUMECHANGE) + smp_call_function_single(freqs->cpu, twd_update_frequency, + NULL, 1); + + return NOTIFY_OK; +} + +static struct notifier_block twd_cpufreq_nb = { + .notifier_call = twd_cpufreq_transition, +}; + +static int twd_cpufreq_init(void) +{ + if (!IS_ERR_OR_NULL(twd_clk)) + return cpufreq_register_notifier(&twd_cpufreq_nb, + CPUFREQ_TRANSITION_NOTIFIER); + + return 0; +} +core_initcall(twd_cpufreq_init); + static void __cpuinit twd_calibrate_rate(void) { unsigned long count; @@ -124,7 +175,16 @@ static void __cpuinit twd_calibrate_rate(void) */ void __cpuinit twd_timer_setup(struct clock_event_device *clk) { - twd_calibrate_rate(); + if (twd_clk == NULL) { + twd_clk = clk_get_sys("smp_twd", NULL); + if (IS_ERR_OR_NULL(twd_clk)) + pr_warn("%s: no clock found\n", __func__); + } + + if (!IS_ERR_OR_NULL(twd_clk)) + twd_timer_rate = clk_get_rate(twd_clk); + else + twd_calibrate_rate(); clk->name = "local_timer"; clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT | @@ -132,13 +192,16 @@ void __cpuinit twd_timer_setup(struct clock_event_device *clk) clk->rating = 350; clk->set_mode = twd_set_mode; clk->set_next_event = twd_set_next_event; - clk->shift = 20; - clk->mult = div_sc(twd_timer_rate, NSEC_PER_SEC, clk->shift); + + clockevents_calc_mult_shift(clk, twd_timer_rate, TWD_MIN_RANGE); + clk->max_delta_ns = clockevent_delta2ns(0xffffffff, clk); clk->min_delta_ns = clockevent_delta2ns(0xf, clk); /* Make sure our local interrupt controller has this enabled */ gic_enable_ppi(clk->irq); + __get_cpu_var(twd_ce) = clk; + clockevents_register_device(clk); } diff --git a/arch/arm/mach-omap1/Makefile b/arch/arm/mach-omap1/Makefile index af98117043d2..e5082b000d0b 100644 --- a/arch/arm/mach-omap1/Makefile +++ b/arch/arm/mach-omap1/Makefile @@ -10,6 +10,9 @@ obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o obj-$(CONFIG_OMAP_32K_TIMER) += timer32k.o +# CPUFREQ driver +obj-$(CONFIG_CPU_FREQ) += omap1-cpufreq.o + # Power Management obj-$(CONFIG_PM) += pm.o sleep.o pm_bus.o diff --git a/arch/arm/plat-omap/cpu-omap.c b/arch/arm/mach-omap1/omap1-cpufreq.c index da4f68dbba1d..7c5216eec131 100644 --- a/arch/arm/plat-omap/cpu-omap.c +++ b/arch/arm/mach-omap1/omap1-cpufreq.c @@ -1,5 +1,5 @@ /* - * linux/arch/arm/plat-omap/cpu-omap.c + * OMAP1 cpufreq driver * * CPU frequency scaling for OMAP * @@ -8,6 +8,9 @@ * * Based on cpu-sa1110.c, Copyright (C) 2001 Russell King * + * 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. @@ -21,25 +24,20 @@ #include <linux/err.h> #include <linux/clk.h> #include <linux/io.h> +#include <linux/opp.h> -#include <mach/hardware.h> -#include <plat/clock.h> #include <asm/system.h> -#define VERY_HI_RATE 900000000 +#include <plat/clock.h> +#include <plat/omap-pm.h> -static struct cpufreq_frequency_table *freq_table; +#include <mach/hardware.h> -#ifdef CONFIG_ARCH_OMAP1 -#define MPU_CLK "mpu" -#else -#define MPU_CLK "virt_prcm_set" -#endif +#define VERY_HI_RATE 900000000 +static struct cpufreq_frequency_table *freq_table; static struct clk *mpu_clk; -/* TODO: Add support for SDRAM timing changes */ - static int omap_verify_speed(struct cpufreq_policy *policy) { if (freq_table) @@ -91,21 +89,22 @@ static int omap_target(struct cpufreq_policy *policy, return ret; cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + #ifdef CONFIG_CPU_FREQ_DEBUG - printk(KERN_DEBUG "cpufreq-omap: transition: %u --> %u\n", - freqs.old, freqs.new); + pr_info("cpufreq-omap: transition: %u --> %u\n", freqs.old, freqs.new); #endif ret = clk_set_rate(mpu_clk, freqs.new * 1000); + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); return ret; } -static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy) +static int __init omap_cpu_init(struct cpufreq_policy *policy) { int result = 0; - mpu_clk = clk_get(NULL, MPU_CLK); + mpu_clk = clk_get(NULL, "mpu"); if (IS_ERR(mpu_clk)) return PTR_ERR(mpu_clk); @@ -115,6 +114,7 @@ static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy) policy->cur = policy->min = policy->max = omap_getspeed(0); clk_init_cpufreq_table(&freq_table); + if (freq_table) { result = cpufreq_frequency_table_cpuinfo(policy, freq_table); if (!result) @@ -126,6 +126,10 @@ static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy) VERY_HI_RATE) / 1000; } + policy->min = policy->cpuinfo.min_freq; + policy->max = policy->cpuinfo.max_freq; + policy->cur = omap_getspeed(0); + /* FIXME: what's the actual transition time? */ policy->cpuinfo.transition_latency = 300 * 1000; @@ -151,7 +155,7 @@ static struct cpufreq_driver omap_driver = { .get = omap_getspeed, .init = omap_cpu_init, .exit = omap_cpu_exit, - .name = "omap", + .name = "omap1", .attr = omap_cpufreq_attr, }; @@ -160,12 +164,12 @@ static int __init omap_cpufreq_init(void) return cpufreq_register_driver(&omap_driver); } -arch_initcall(omap_cpufreq_init); - -/* - * if ever we want to remove this, upon cleanup call: - * - * cpufreq_unregister_driver() - * cpufreq_frequency_table_put_attr() - */ +static void __exit omap_cpufreq_exit(void) +{ + cpufreq_unregister_driver(&omap_driver); +} +MODULE_DESCRIPTION("cpufreq driver for OMAP1 SOCs"); +MODULE_LICENSE("GPL"); +module_init(omap_cpufreq_init); +module_exit(omap_cpufreq_exit); diff --git a/arch/arm/mach-omap2/Kconfig b/arch/arm/mach-omap2/Kconfig index b997a35830fc..9aeb204dfa4e 100644 --- a/arch/arm/mach-omap2/Kconfig +++ b/arch/arm/mach-omap2/Kconfig @@ -313,6 +313,7 @@ config MACH_OMAP_4430SDP select OMAP_PACKAGE_CBL select OMAP_PACKAGE_CBS select REGULATOR_FIXED_VOLTAGE + select OMAP_TPS6236X config MACH_OMAP4_PANDA bool "OMAP4 Panda Board" @@ -341,6 +342,18 @@ config OMAP3_SDRC_AC_TIMING wish to say no. Selecting yes without understanding what is going on could result in system crashes; +config OMAP_ALLOW_OSWR + bool "Enable Open Switch Retention" + depends on ARCH_OMAP4 + default n + help + Select this option to enable OSWR support. + Which means the Logic of power domains can be lost now + unlike the CSWR wherein the logic is retained + +config OMAP_TPS6236X + bool + endmenu endif diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile index bc7b31b0ae19..6fc4124accc9 100644 --- a/arch/arm/mach-omap2/Makefile +++ b/arch/arm/mach-omap2/Makefile @@ -4,7 +4,7 @@ # Common support obj-y := id.o io.o control.o mux.o devices.o serial.o gpmc.o timer-gp.o pm.o \ - common.o gpio.o dma.o wd_timer.o + common.o gpio.o dma.o wd_timer.o omap_pmic.o omap-2-3-common = irq.o sdrc.o hwmod-common = omap_hwmod.o \ @@ -19,12 +19,14 @@ obj-$(CONFIG_ARCH_OMAP4) += prm44xx.o $(hwmod-common) obj-$(CONFIG_OMAP_MCBSP) += mcbsp.o obj-$(CONFIG_TWL4030_CORE) += omap_twl.o +obj-$(CONFIG_OMAP_TPS6236X) += omap_tps6236x.o # SMP support ONLY available for OMAP4 obj-$(CONFIG_SMP) += omap-smp.o omap-headsmp.o obj-$(CONFIG_LOCAL_TIMERS) += timer-mpu.o obj-$(CONFIG_HOTPLUG_CPU) += omap-hotplug.o -obj-$(CONFIG_ARCH_OMAP4) += omap44xx-smc.o omap4-common.o +obj-$(CONFIG_ARCH_OMAP4) += omap44xx-smc.o omap4-common.o \ + omap-wakeupgen.o plus_sec := $(call as-instr,.arch_extension sec,+sec) AFLAGS_omap-headsmp.o :=-Wa,-march=armv7-a$(plus_sec) @@ -54,21 +56,27 @@ ifeq ($(CONFIG_PM_OPP),y) obj-y += opp.o obj-$(CONFIG_ARCH_OMAP3) += opp3xxx_data.o obj-$(CONFIG_ARCH_OMAP4) += opp4xxx_data.o +# CPUFREQ driver +obj-$(CONFIG_CPU_FREQ) += omap2plus-cpufreq.o endif + # Power Management ifeq ($(CONFIG_PM),y) obj-$(CONFIG_ARCH_OMAP2) += pm24xx.o obj-$(CONFIG_ARCH_OMAP2) += sleep24xx.o pm_bus.o obj-$(CONFIG_ARCH_OMAP3) += pm34xx.o sleep34xx.o \ cpuidle34xx.o pm_bus.o -obj-$(CONFIG_ARCH_OMAP4) += pm44xx.o pm_bus.o +obj-$(CONFIG_ARCH_OMAP4) += pm44xx.o pm_bus.o \ + omap4-mpuss-lowpower.o sleep44xx.o \ + cpuidle44xx.o obj-$(CONFIG_PM_DEBUG) += pm-debug.o obj-$(CONFIG_OMAP_SMARTREFLEX) += sr_device.o smartreflex.o obj-$(CONFIG_OMAP_SMARTREFLEX_CLASS3) += smartreflex-class3.o AFLAGS_sleep24xx.o :=-Wa,-march=armv6 AFLAGS_sleep34xx.o :=-Wa,-march=armv7-a$(plus_sec) +AFLAGS_sleep44xx.o :=-Wa,-march=armv7-a ifeq ($(CONFIG_PM_VERBOSE),y) CFLAGS_pm_bus.o += -DDEBUG @@ -79,19 +87,20 @@ endif # PRCM obj-$(CONFIG_ARCH_OMAP2) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o obj-$(CONFIG_ARCH_OMAP3) += prcm.o cm2xxx_3xxx.o prm2xxx_3xxx.o \ - vc3xxx_data.o vp3xxx_data.o + vc3xxx_data.o vp3xxx_data.o dvfs.o # XXX The presence of cm2xxx_3xxx.o on the line below is temporary and # will be removed once the OMAP4 part of the codebase is converted to # use OMAP4-specific PRCM functions. obj-$(CONFIG_ARCH_OMAP4) += prcm.o cm2xxx_3xxx.o cminst44xx.o \ cm44xx.o prcm_mpu44xx.o \ prminst44xx.o vc44xx_data.o \ - vp44xx_data.o + vp44xx_data.o dvfs.o # OMAP voltage domains ifeq ($(CONFIG_PM),y) -voltagedomain-common := voltage.o -obj-$(CONFIG_ARCH_OMAP2) += $(voltagedomain-common) +voltagedomain-common := voltage.o vc.o vp.o +obj-$(CONFIG_ARCH_OMAP2) += $(voltagedomain-common) \ + voltagedomains2xxx_data.o obj-$(CONFIG_ARCH_OMAP3) += $(voltagedomain-common) \ voltagedomains3xxx_data.o obj-$(CONFIG_ARCH_OMAP4) += $(voltagedomain-common) \ @@ -157,6 +166,9 @@ obj-$(CONFIG_OMAP3_EMU) += emu.o obj-$(CONFIG_ARCH_OMAP3) += omap_l3_smx.o obj-$(CONFIG_ARCH_OMAP4) += omap_l3_noc.o +# LDO stuff +obj-$(CONFIG_ARCH_OMAP4) += omap4_trim_quirks.o + obj-$(CONFIG_OMAP_MBOX_FWK) += mailbox_mach.o mailbox_mach-objs := mailbox.o diff --git a/arch/arm/mach-omap2/board-3430sdp.c b/arch/arm/mach-omap2/board-3430sdp.c index 9afd087cc29c..7ffad7bfb820 100644 --- a/arch/arm/mach-omap2/board-3430sdp.c +++ b/arch/arm/mach-omap2/board-3430sdp.c @@ -59,24 +59,6 @@ #define TWL4030_MSECURE_GPIO 22 -/* FIXME: These values need to be updated based on more profiling on 3430sdp*/ -static struct cpuidle_params omap3_cpuidle_params_table[] = { - /* C1 */ - {1, 2, 2, 5}, - /* C2 */ - {1, 10, 10, 30}, - /* C3 */ - {1, 50, 50, 300}, - /* C4 */ - {1, 1500, 1800, 4000}, - /* C5 */ - {1, 2500, 7500, 12000}, - /* C6 */ - {1, 3000, 8500, 15000}, - /* C7 */ - {1, 10000, 30000, 300000}, -}; - static uint32_t board_keymap[] = { KEY(0, 0, KEY_LEFT), KEY(0, 1, KEY_RIGHT), @@ -883,7 +865,6 @@ static void __init omap_3430sdp_init(void) omap3_mux_init(board_mux, OMAP_PACKAGE_CBB); omap_board_config = sdp3430_config; omap_board_config_size = ARRAY_SIZE(sdp3430_config); - omap3_pm_init_cpuidle(omap3_cpuidle_params_table); omap3430_i2c_init(); omap_display_init(&sdp3430_dss_data); if (omap_rev() > OMAP3430_REV_ES1_0) diff --git a/arch/arm/mach-omap2/board-4430sdp.c b/arch/arm/mach-omap2/board-4430sdp.c index 7c0b066eaf0c..821b5ae6d661 100644 --- a/arch/arm/mach-omap2/board-4430sdp.c +++ b/arch/arm/mach-omap2/board-4430sdp.c @@ -43,6 +43,7 @@ #include "hsmmc.h" #include "timer-gp.h" #include "control.h" +#include "pm.h" #define ETH_KS8851_IRQ 34 #define ETH_KS8851_POWER_ON 48 @@ -57,6 +58,8 @@ #define LED_PWM2OFF 0x04 #define TWL6030_TOGGLE3 0x92 +#define TPS62361_GPIO 7 + static const int sdp4430_keymap[] = { KEY(0, 0, KEY_E), KEY(0, 1, KEY_R), @@ -940,6 +943,14 @@ static void __init omap_4430sdp_init(void) pr_err("Keypad initialization failed: %d\n", status); omap_4430sdp_display_init(); + + if (cpu_is_omap446x()) { + /* Vsel0 = gpio, vsel1 = gnd */ + status = omap_tps6236x_board_setup(true, TPS62361_GPIO, -1, + OMAP_PIN_OFF_OUTPUT_HIGH, -1); + if (status) + pr_err("TPS62361 initialization failed: %d\n", status); + } } static void __init omap_4430sdp_map_io(void) diff --git a/arch/arm/mach-omap2/board-omap4panda.c b/arch/arm/mach-omap2/board-omap4panda.c index f70850e6a5ab..340ed480e2a5 100644 --- a/arch/arm/mach-omap2/board-omap4panda.c +++ b/arch/arm/mach-omap2/board-omap4panda.c @@ -727,7 +727,13 @@ static void __init omap4_panda_init(void) omap4_panda_i2c_init(); platform_add_devices(panda_devices, ARRAY_SIZE(panda_devices)); - platform_device_register(&omap_vwlan_device); +/* + * This is temporaray. With WLAN regsitering, we see that UART2 is not + * idling on panda and CORE RET is not happening. So removing this FTM. + * Later will be enabled. + * + * platform_device_register(&omap_vwlan_device); + */ board_serial_init(); omap4_twl6030_hsmmc_init(mmc); omap4_ehci_init(); diff --git a/arch/arm/mach-omap2/board-rx51.c b/arch/arm/mach-omap2/board-rx51.c index f8ba20a14e62..fec4cac8fa0a 100644 --- a/arch/arm/mach-omap2/board-rx51.c +++ b/arch/arm/mach-omap2/board-rx51.c @@ -58,21 +58,25 @@ static struct platform_device leds_gpio = { }, }; +/* + * cpuidle C-states definition override from the default values. + * The 'exit_latency' field is the sum of sleep and wake-up latencies. + */ static struct cpuidle_params rx51_cpuidle_params[] = { /* C1 */ - {1, 110, 162, 5}, + {110 + 162, 5 , 1}, /* C2 */ - {1, 106, 180, 309}, + {106 + 180, 309, 1}, /* C3 */ - {0, 107, 410, 46057}, + {107 + 410, 46057, 0}, /* C4 */ - {0, 121, 3374, 46057}, + {121 + 3374, 46057, 0}, /* C5 */ - {1, 855, 1146, 46057}, + {855 + 1146, 46057, 1}, /* C6 */ - {0, 7580, 4134, 484329}, + {7580 + 4134, 484329, 0}, /* C7 */ - {1, 7505, 15274, 484329}, + {7505 + 15274, 484329, 1}, }; static struct omap_lcd_config rx51_lcd_config = { diff --git a/arch/arm/mach-omap2/clkt_dpll.c b/arch/arm/mach-omap2/clkt_dpll.c index bcffee001bfa..a7e78e4104f4 100644 --- a/arch/arm/mach-omap2/clkt_dpll.c +++ b/arch/arm/mach-omap2/clkt_dpll.c @@ -292,12 +292,14 @@ long omap2_dpll_round_rate(struct clk *clk, unsigned long target_rate) for (n = dd->min_divider; n <= dd->max_divider; n++) { - /* Is the (input clk, divider) pair valid for the DPLL? */ - r = _dpll_test_fint(clk, n); - if (r == DPLL_FINT_UNDERFLOW) - break; - else if (r == DPLL_FINT_INVALID) - continue; + if (cpu_is_omap34xx()) { + /* Is the (input clk, divider)pair valid for the DPLL?*/ + r = _dpll_test_fint(clk, n); + if (r == DPLL_FINT_UNDERFLOW) + break; + else if (r == DPLL_FINT_INVALID) + continue; + } /* Compute the scaled DPLL multiplier, based on the divider */ m = scaled_rt_rp * n; diff --git a/arch/arm/mach-omap2/clock.h b/arch/arm/mach-omap2/clock.h index 5278e7339fbb..bfc87255c48a 100644 --- a/arch/arm/mach-omap2/clock.h +++ b/arch/arm/mach-omap2/clock.h @@ -64,6 +64,9 @@ void omap3_noncore_dpll_disable(struct clk *clk); int omap4_dpllmx_gatectrl_read(struct clk *clk); void omap4_dpllmx_allow_gatectrl(struct clk *clk); void omap4_dpllmx_deny_gatectrl(struct clk *clk); +int omap4460_mpu_dpll_set_rate(struct clk *clk, unsigned long rate); +long omap4460_mpu_dpll_round_rate(struct clk *clk, unsigned long rate); +unsigned long omap4460_mpu_dpll_recalc(struct clk *clk); #ifdef CONFIG_OMAP_RESET_CLOCKS void omap2_clk_disable_unused(struct clk *clk); @@ -142,7 +145,9 @@ extern const struct clksel_rate gpt_sys_rates[]; extern const struct clksel_rate gfx_l3_rates[]; extern const struct clksel_rate dsp_ick_rates[]; -#if defined(CONFIG_ARCH_OMAP2) && defined(CONFIG_CPU_FREQ) +#ifdef CONFIG_CPU_FREQ + +#ifdef CONFIG_ARCH_OMAP2 extern void omap2_clk_init_cpufreq_table(struct cpufreq_frequency_table **table); extern void omap2_clk_exit_cpufreq_table(struct cpufreq_frequency_table **table); #else @@ -150,6 +155,16 @@ extern void omap2_clk_exit_cpufreq_table(struct cpufreq_frequency_table **table) #define omap2_clk_exit_cpufreq_table 0 #endif +#ifdef CONFIG_ARCH_OMAP3 +extern void omap3_clk_init_cpufreq_table(struct cpufreq_frequency_table **table); +extern void omap3_clk_exit_cpufreq_table(struct cpufreq_frequency_table **table); +#else +#define omap3_clk_init_cpufreq_table 0 +#define omap3_clk_exit_cpufreq_table 0 +#endif + +#endif /* CONFIG_CPU_FREQ */ + extern const struct clkops clkops_omap2_iclk_dflt_wait; extern const struct clkops clkops_omap2_iclk_dflt; extern const struct clkops clkops_omap2_iclk_idle_only; diff --git a/arch/arm/mach-omap2/clock34xx.c b/arch/arm/mach-omap2/clock34xx.c index 1fc96b9ee330..119e135c30a6 100644 --- a/arch/arm/mach-omap2/clock34xx.c +++ b/arch/arm/mach-omap2/clock34xx.c @@ -20,6 +20,8 @@ #include <linux/kernel.h> #include <linux/clk.h> #include <linux/io.h> +#include <linux/err.h> +#include <linux/cpufreq.h> #include <plat/clock.h> diff --git a/arch/arm/mach-omap2/clock44xx_data.c b/arch/arm/mach-omap2/clock44xx_data.c index 7fd268fd6bc4..c45556bb481a 100644 --- a/arch/arm/mach-omap2/clock44xx_data.c +++ b/arch/arm/mach-omap2/clock44xx_data.c @@ -774,6 +774,15 @@ static struct clk dpll_mpu_m2_ck = { .set_rate = &omap2_clksel_set_rate, }; +static struct clk virt_dpll_mpu_ck = { + .name = "virt_dpll_mpu_ck", + .parent = &dpll_mpu_ck, + .ops = &clkops_null, + .recalc = &omap4460_mpu_dpll_recalc, + .round_rate = &omap4460_mpu_dpll_round_rate, + .set_rate = &omap4460_mpu_dpll_set_rate, +}; + static struct clk per_hs_clk_div_ck = { .name = "per_hs_clk_div_ck", .parent = &dpll_abe_m3x2_ck, @@ -3027,6 +3036,14 @@ static struct clk auxclkreq5_ck = { .recalc = &omap2_clksel_recalc, }; +static struct clk smp_twd = { + .name = "smp_twd", + .parent = &dpll_mpu_ck, + .ops = &clkops_null, + .fixed_div = 2, + .recalc = &omap_fixed_divisor_recalc, +}; + /* * clkdev */ @@ -3082,6 +3099,7 @@ static struct omap_clk omap44xx_clks[] = { CLK(NULL, "dpll_iva_m4x2_ck", &dpll_iva_m4x2_ck, CK_44XX), CLK(NULL, "dpll_iva_m5x2_ck", &dpll_iva_m5x2_ck, CK_44XX), CLK(NULL, "dpll_mpu_ck", &dpll_mpu_ck, CK_44XX), + CLK(NULL, "virt_dpll_mpu_ck", &virt_dpll_mpu_ck, CK_446X), CLK(NULL, "dpll_mpu_m2_ck", &dpll_mpu_m2_ck, CK_44XX), CLK(NULL, "per_hs_clk_div_ck", &per_hs_clk_div_ck, CK_44XX), CLK(NULL, "per_hsd_byp_clk_mux_ck", &per_hsd_byp_clk_mux_ck, CK_44XX), @@ -3302,6 +3320,7 @@ static struct omap_clk omap44xx_clks[] = { CLK(NULL, "auxclkreq3_ck", &auxclkreq3_ck, CK_44XX), CLK(NULL, "auxclkreq4_ck", &auxclkreq4_ck, CK_44XX), CLK(NULL, "auxclkreq5_ck", &auxclkreq5_ck, CK_44XX), + CLK(NULL, "smp_twd", &smp_twd, CK_44XX), }; int __init omap4xxx_clk_init(void) diff --git a/arch/arm/mach-omap2/clockdomain.c b/arch/arm/mach-omap2/clockdomain.c index 0e9aa162754b..b98a97226e0a 100644 --- a/arch/arm/mach-omap2/clockdomain.c +++ b/arch/arm/mach-omap2/clockdomain.c @@ -795,6 +795,27 @@ void clkdm_deny_idle(struct clockdomain *clkdm) arch_clkdm->clkdm_deny_idle(clkdm); } +/** + * clkdm_is_idle - Check if the clkdm hwsup/autoidle is enabled + * @clkdm: struct clockdomain * + * + * Returns true if the clockdomain is in hardware-supervised + * idle mode, or 0 otherwise. + * + */ +int clkdm_is_idle(struct clockdomain *clkdm) +{ + if (!clkdm) + return -EINVAL; + + if (!arch_clkdm || !arch_clkdm->clkdm_is_idle) + return -EINVAL; + + pr_debug("clockdomain: reading idle state for %s\n", clkdm->name); + + return arch_clkdm->clkdm_is_idle(clkdm); +} + /* Clockdomain-to-clock framework interface code */ diff --git a/arch/arm/mach-omap2/clockdomain.h b/arch/arm/mach-omap2/clockdomain.h index 5823584d9cd7..085ed823ffba 100644 --- a/arch/arm/mach-omap2/clockdomain.h +++ b/arch/arm/mach-omap2/clockdomain.h @@ -138,6 +138,7 @@ struct clockdomain { * @clkdm_wakeup: Force a clockdomain to wakeup * @clkdm_allow_idle: Enable hw supervised idle transitions for clock domain * @clkdm_deny_idle: Disable hw supervised idle transitions for clock domain + * @clkdm_is_idle: Check if hw supervised idle transitions are enabled * @clkdm_clk_enable: Put the clkdm in right state for a clock enable * @clkdm_clk_disable: Put the clkdm in right state for a clock disable */ @@ -154,6 +155,7 @@ struct clkdm_ops { int (*clkdm_wakeup)(struct clockdomain *clkdm); void (*clkdm_allow_idle)(struct clockdomain *clkdm); void (*clkdm_deny_idle)(struct clockdomain *clkdm); + int (*clkdm_is_idle)(struct clockdomain *clkdm); int (*clkdm_clk_enable)(struct clockdomain *clkdm); int (*clkdm_clk_disable)(struct clockdomain *clkdm); }; @@ -177,6 +179,7 @@ int clkdm_clear_all_sleepdeps(struct clockdomain *clkdm); void clkdm_allow_idle(struct clockdomain *clkdm); void clkdm_deny_idle(struct clockdomain *clkdm); +int clkdm_is_idle(struct clockdomain *clkdm); int clkdm_wakeup(struct clockdomain *clkdm); int clkdm_sleep(struct clockdomain *clkdm); diff --git a/arch/arm/mach-omap2/clockdomain2xxx_3xxx.c b/arch/arm/mach-omap2/clockdomain2xxx_3xxx.c index 48d0db7e6069..db49baadcec9 100644 --- a/arch/arm/mach-omap2/clockdomain2xxx_3xxx.c +++ b/arch/arm/mach-omap2/clockdomain2xxx_3xxx.c @@ -13,6 +13,7 @@ */ #include <linux/types.h> +#include <linux/errno.h> #include <plat/prcm.h> #include "prm.h" #include "prm2xxx_3xxx.h" @@ -146,6 +147,15 @@ static void omap2_clkdm_deny_idle(struct clockdomain *clkdm) _clkdm_del_autodeps(clkdm); } +static int omap2_clkdm_is_idle(struct clockdomain *clkdm) +{ + if (!clkdm->clktrctrl_mask) + return -1; + + return omap2_cm_is_clkdm_in_hwsup(clkdm->pwrdm.ptr->prcm_offs, + clkdm->clktrctrl_mask); +} + static void _enable_hwsup(struct clockdomain *clkdm) { if (cpu_is_omap24xx()) @@ -252,6 +262,7 @@ struct clkdm_ops omap2_clkdm_operations = { .clkdm_wakeup = omap2_clkdm_wakeup, .clkdm_allow_idle = omap2_clkdm_allow_idle, .clkdm_deny_idle = omap2_clkdm_deny_idle, + .clkdm_is_idle = omap2_clkdm_is_idle, .clkdm_clk_enable = omap2_clkdm_clk_enable, .clkdm_clk_disable = omap2_clkdm_clk_disable, }; @@ -269,6 +280,7 @@ struct clkdm_ops omap3_clkdm_operations = { .clkdm_wakeup = omap3_clkdm_wakeup, .clkdm_allow_idle = omap3_clkdm_allow_idle, .clkdm_deny_idle = omap3_clkdm_deny_idle, + .clkdm_is_idle = omap2_clkdm_is_idle, .clkdm_clk_enable = omap2_clkdm_clk_enable, .clkdm_clk_disable = omap2_clkdm_clk_disable, }; diff --git a/arch/arm/mach-omap2/clockdomain44xx.c b/arch/arm/mach-omap2/clockdomain44xx.c index 017fb14b3022..0f6b8134ea19 100644 --- a/arch/arm/mach-omap2/clockdomain44xx.c +++ b/arch/arm/mach-omap2/clockdomain44xx.c @@ -93,6 +93,12 @@ static void omap4_clkdm_deny_idle(struct clockdomain *clkdm) clkdm->cm_inst, clkdm->clkdm_offs); } +static int omap4_clkdm_is_idle(struct clockdomain *clkdm) +{ + return omap4_cminst_is_clkdm_in_hwsup(clkdm->prcm_partition, + clkdm->cm_inst, clkdm->clkdm_offs); +} + static int omap4_clkdm_clk_enable(struct clockdomain *clkdm) { bool hwsup = false; @@ -129,6 +135,7 @@ struct clkdm_ops omap4_clkdm_operations = { .clkdm_wakeup = omap4_clkdm_wakeup, .clkdm_allow_idle = omap4_clkdm_allow_idle, .clkdm_deny_idle = omap4_clkdm_deny_idle, + .clkdm_is_idle = omap4_clkdm_is_idle, .clkdm_clk_enable = omap4_clkdm_clk_enable, .clkdm_clk_disable = omap4_clkdm_clk_disable, }; diff --git a/arch/arm/mach-omap2/clockdomains44xx_data.c b/arch/arm/mach-omap2/clockdomains44xx_data.c index f9c980e96da6..1bdf360a1602 100644 --- a/arch/arm/mach-omap2/clockdomains44xx_data.c +++ b/arch/arm/mach-omap2/clockdomains44xx_data.c @@ -493,7 +493,7 @@ static struct clockdomain l3_init_44xx_clkdm = { .dep_bit = OMAP4430_L3INIT_STATDEP_SHIFT, .wkdep_srcs = l3_init_wkup_sleep_deps, .sleepdep_srcs = l3_init_wkup_sleep_deps, - .flags = CLKDM_CAN_HWSUP_SWSUP, + .flags = CLKDM_CAN_SWSUP, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), }; diff --git a/arch/arm/mach-omap2/control.c b/arch/arm/mach-omap2/control.c index da53ba3917ca..d480421cf692 100644 --- a/arch/arm/mach-omap2/control.c +++ b/arch/arm/mach-omap2/control.c @@ -204,11 +204,31 @@ void omap_ctrl_writel(u32 val, u16 offset) * registers. This APIs will work only for OMAP4 */ +u8 omap4_ctrl_pad_readb(u16 offset) +{ + return __raw_readb(OMAP4_CTRL_PAD_REGADDR(offset)); +} + +u16 omap4_ctrl_pad_readw(u16 offset) +{ + return __raw_readw(OMAP4_CTRL_PAD_REGADDR(offset)); +} + u32 omap4_ctrl_pad_readl(u16 offset) { return __raw_readl(OMAP4_CTRL_PAD_REGADDR(offset)); } +void omap4_ctrl_pad_writeb(u8 val, u16 offset) +{ + __raw_writeb(val, OMAP4_CTRL_PAD_REGADDR(offset)); +} + +void omap4_ctrl_pad_writew(u16 val, u16 offset) +{ + __raw_writew(val, OMAP4_CTRL_PAD_REGADDR(offset)); +} + void omap4_ctrl_pad_writel(u32 val, u16 offset) { __raw_writel(val, OMAP4_CTRL_PAD_REGADDR(offset)); diff --git a/arch/arm/mach-omap2/control.h b/arch/arm/mach-omap2/control.h index c2804c1c4efd..bc8a3eb9c315 100644 --- a/arch/arm/mach-omap2/control.h +++ b/arch/arm/mach-omap2/control.h @@ -195,6 +195,7 @@ #define OMAP44XX_CONTROL_FUSE_MPU_OPPNITRO 0x249 #define OMAP44XX_CONTROL_FUSE_CORE_OPP50 0x254 #define OMAP44XX_CONTROL_FUSE_CORE_OPP100 0x257 +#define OMAP44XX_CONTROL_FUSE_CORE_OPP100OV 0x25A /* AM35XX only CONTROL_GENERAL register offsets */ #define AM35XX_CONTROL_MSUSPENDMUX_6 (OMAP2_CONTROL_GENERAL + 0x0038) @@ -338,6 +339,14 @@ #define AM35XX_HECC_SW_RST BIT(3) #define AM35XX_VPFE_PCLK_SW_RST BIT(4) +/* CONTROL_PADCONF_X bits */ +#define OMAP44XX_PADCONF_WAKEUPEVENT0 (1 << 15) +#define OMAP44XX_PADCONF_WAKEUPEVENT1 (1 << 30) +#define OMAP44XX_PADCONF_WAKEUPENABLE0 (1 << 14) +#define OMAP44XX_PADCONF_WAKEUPENABLE1 (1 << 31) +#define OMAP44XX_PADCONF_OFFMODEENABLE0 (1 << 9) +#define OMAP44XX_PADCONF_OFFMODEENABLE1 (1 << 25) + /* * CONTROL OMAP STATUS register to identify OMAP3 features */ @@ -378,10 +387,14 @@ extern void __iomem *omap_ctrl_base_get(void); extern u8 omap_ctrl_readb(u16 offset); extern u16 omap_ctrl_readw(u16 offset); extern u32 omap_ctrl_readl(u16 offset); +extern u8 omap4_ctrl_pad_readb(u16 offset); +extern u16 omap4_ctrl_pad_readw(u16 offset); extern u32 omap4_ctrl_pad_readl(u16 offset); extern void omap_ctrl_writeb(u8 val, u16 offset); extern void omap_ctrl_writew(u16 val, u16 offset); extern void omap_ctrl_writel(u32 val, u16 offset); +extern void omap4_ctrl_pad_writeb(u8 val, u16 offset); +extern void omap4_ctrl_pad_writew(u16 val, u16 offset); extern void omap4_ctrl_pad_writel(u32 val, u16 offset); extern void omap3_save_scratchpad_contents(void); diff --git a/arch/arm/mach-omap2/cpuidle34xx.c b/arch/arm/mach-omap2/cpuidle34xx.c index 1c240eff3918..4bf6e6e8b100 100644 --- a/arch/arm/mach-omap2/cpuidle34xx.c +++ b/arch/arm/mach-omap2/cpuidle34xx.c @@ -36,36 +36,6 @@ #ifdef CONFIG_CPU_IDLE -#define OMAP3_MAX_STATES 7 -#define OMAP3_STATE_C1 0 /* C1 - MPU WFI + Core active */ -#define OMAP3_STATE_C2 1 /* C2 - MPU WFI + Core inactive */ -#define OMAP3_STATE_C3 2 /* C3 - MPU CSWR + Core inactive */ -#define OMAP3_STATE_C4 3 /* C4 - MPU OFF + Core iactive */ -#define OMAP3_STATE_C5 4 /* C5 - MPU RET + Core RET */ -#define OMAP3_STATE_C6 5 /* C6 - MPU OFF + Core RET */ -#define OMAP3_STATE_C7 6 /* C7 - MPU OFF + Core OFF */ - -#define OMAP3_STATE_MAX OMAP3_STATE_C7 - -#define CPUIDLE_FLAG_CHECK_BM 0x10000 /* use omap3_enter_idle_bm() */ - -struct omap3_processor_cx { - u8 valid; - u8 type; - u32 sleep_latency; - u32 wakeup_latency; - u32 mpu_state; - u32 core_state; - u32 threshold; - u32 flags; - const char *desc; -}; - -struct omap3_processor_cx omap3_power_states[OMAP3_MAX_STATES]; -struct omap3_processor_cx current_cx_state; -struct powerdomain *mpu_pd, *core_pd, *per_pd; -struct powerdomain *cam_pd; - /* * The latencies/thresholds for various C states have * to be configured from the respective board files. @@ -75,27 +45,31 @@ struct powerdomain *cam_pd; */ static struct cpuidle_params cpuidle_params_table[] = { /* C1 */ - {1, 2, 2, 5}, + {2 + 2, 5, 1}, /* C2 */ - {1, 10, 10, 30}, + {10 + 10, 30, 1}, /* C3 */ - {1, 50, 50, 300}, + {50 + 50, 300, 1}, /* C4 */ - {1, 1500, 1800, 4000}, + {1500 + 1800, 4000, 1}, /* C5 */ - {1, 2500, 7500, 12000}, + {2500 + 7500, 12000, 1}, /* C6 */ - {1, 3000, 8500, 15000}, + {3000 + 8500, 15000, 1}, /* C7 */ - {1, 10000, 30000, 300000}, + {10000 + 30000, 300000, 1}, }; +#define OMAP3_NUM_STATES ARRAY_SIZE(cpuidle_params_table) -static int omap3_idle_bm_check(void) -{ - if (!omap3_can_sleep()) - return 1; - return 0; -} +/* Mach specific information to be recorded in the C-state driver_data */ +struct omap3_idle_statedata { + u32 mpu_state; + u32 core_state; + u8 valid; +}; +struct omap3_idle_statedata omap3_idle_data[OMAP3_NUM_STATES]; + +struct powerdomain *mpu_pd, *core_pd, *per_pd, *cam_pd; static int _cpuidle_allow_idle(struct powerdomain *pwrdm, struct clockdomain *clkdm) @@ -122,12 +96,10 @@ static int _cpuidle_deny_idle(struct powerdomain *pwrdm, static int omap3_enter_idle(struct cpuidle_device *dev, struct cpuidle_state *state) { - struct omap3_processor_cx *cx = cpuidle_get_statedata(state); + struct omap3_idle_statedata *cx = cpuidle_get_statedata(state); struct timespec ts_preidle, ts_postidle, ts_idle; u32 mpu_state = cx->mpu_state, core_state = cx->core_state; - current_cx_state = *cx; - /* Used to keep track of the total time in idle */ getnstimeofday(&ts_preidle); @@ -140,7 +112,8 @@ static int omap3_enter_idle(struct cpuidle_device *dev, if (omap_irq_pending() || need_resched()) goto return_sleep_time; - if (cx->type == OMAP3_STATE_C1) { + /* Deny idle for C1 */ + if (state == &dev->states[0]) { pwrdm_for_each_clkdm(mpu_pd, _cpuidle_deny_idle); pwrdm_for_each_clkdm(core_pd, _cpuidle_deny_idle); } @@ -148,7 +121,8 @@ static int omap3_enter_idle(struct cpuidle_device *dev, /* Execute ARM wfi */ omap_sram_idle(); - if (cx->type == OMAP3_STATE_C1) { + /* Re-allow idle for C1 */ + if (state == &dev->states[0]) { pwrdm_for_each_clkdm(mpu_pd, _cpuidle_allow_idle); pwrdm_for_each_clkdm(core_pd, _cpuidle_allow_idle); } @@ -164,41 +138,53 @@ return_sleep_time: } /** - * next_valid_state - Find next valid c-state + * next_valid_state - Find next valid C-state * @dev: cpuidle device - * @state: Currently selected c-state + * @state: Currently selected C-state * * If the current state is valid, it is returned back to the caller. * Else, this function searches for a lower c-state which is still - * valid (as defined in omap3_power_states[]). + * valid. + * + * A state is valid if the 'valid' field is enabled and + * if it satisfies the enable_off_mode condition. */ static struct cpuidle_state *next_valid_state(struct cpuidle_device *dev, - struct cpuidle_state *curr) + struct cpuidle_state *curr) { struct cpuidle_state *next = NULL; - struct omap3_processor_cx *cx; + struct omap3_idle_statedata *cx = cpuidle_get_statedata(curr); + u32 mpu_deepest_state = PWRDM_POWER_RET; + u32 core_deepest_state = PWRDM_POWER_RET; - cx = (struct omap3_processor_cx *)cpuidle_get_statedata(curr); + if (enable_off_mode) { + mpu_deepest_state = PWRDM_POWER_OFF; + /* + * Erratum i583: valable for ES rev < Es1.2 on 3630. + * CORE OFF mode is not supported in a stable form, restrict + * instead the CORE state to RET. + */ + if (!IS_PM34XX_ERRATUM(PM_SDRC_WAKEUP_ERRATUM_i583)) + core_deepest_state = PWRDM_POWER_OFF; + } /* Check if current state is valid */ - if (cx->valid) { + if ((cx->valid) && + (cx->mpu_state >= mpu_deepest_state) && + (cx->core_state >= core_deepest_state)) { return curr; } else { - u8 idx = OMAP3_STATE_MAX; + int idx = OMAP3_NUM_STATES - 1; - /* - * Reach the current state starting at highest C-state - */ - for (; idx >= OMAP3_STATE_C1; idx--) { + /* Reach the current state starting at highest C-state */ + for (; idx >= 0; idx--) { if (&dev->states[idx] == curr) { next = &dev->states[idx]; break; } } - /* - * Should never hit this condition. - */ + /* Should never hit this condition */ WARN_ON(next == NULL); /* @@ -206,17 +192,17 @@ static struct cpuidle_state *next_valid_state(struct cpuidle_device *dev, * Start search from the next (lower) state. */ idx--; - for (; idx >= OMAP3_STATE_C1; idx--) { - struct omap3_processor_cx *cx; - + for (; idx >= 0; idx--) { cx = cpuidle_get_statedata(&dev->states[idx]); - if (cx->valid) { + if ((cx->valid) && + (cx->mpu_state >= mpu_deepest_state) && + (cx->core_state >= core_deepest_state)) { next = &dev->states[idx]; break; } } /* - * C1 and C2 are always valid. + * C1 is always valid. * So, no need to check for 'next==NULL' outside this loop. */ } @@ -229,36 +215,22 @@ static struct cpuidle_state *next_valid_state(struct cpuidle_device *dev, * @dev: cpuidle device * @state: The target state to be programmed * - * Used for C states with CPUIDLE_FLAG_CHECK_BM flag set. This - * function checks for any pending activity and then programs the - * device to the specified or a safer state. + * This function checks for any pending activity and then programs + * the device to the specified or a safer state. */ static int omap3_enter_idle_bm(struct cpuidle_device *dev, struct cpuidle_state *state) { - struct cpuidle_state *new_state = next_valid_state(dev, state); - u32 core_next_state, per_next_state = 0, per_saved_state = 0; - u32 cam_state; - struct omap3_processor_cx *cx; + struct cpuidle_state *new_state; + u32 core_next_state, per_next_state = 0, per_saved_state = 0, cam_state; + struct omap3_idle_statedata *cx; int ret; - if ((state->flags & CPUIDLE_FLAG_CHECK_BM) && omap3_idle_bm_check()) { - BUG_ON(!dev->safe_state); + if (!omap3_can_sleep()) { new_state = dev->safe_state; goto select_state; } - cx = cpuidle_get_statedata(state); - core_next_state = cx->core_state; - - /* - * FIXME: we currently manage device-specific idle states - * for PER and CORE in combination with CPU-specific - * idle states. This is wrong, and device-specific - * idle management needs to be separated out into - * its own code. - */ - /* * Prevent idle completely if CAM is active. * CAM does not have wakeup capability in OMAP3. @@ -270,9 +242,19 @@ static int omap3_enter_idle_bm(struct cpuidle_device *dev, } /* + * FIXME: we currently manage device-specific idle states + * for PER and CORE in combination with CPU-specific + * idle states. This is wrong, and device-specific + * idle management needs to be separated out into + * its own code. + */ + + /* * Prevent PER off if CORE is not in retention or off as this * would disable PER wakeups completely. */ + cx = cpuidle_get_statedata(state); + core_next_state = cx->core_state; per_next_state = per_saved_state = pwrdm_read_next_pwrst(per_pd); if ((per_next_state == PWRDM_POWER_OFF) && (core_next_state > PWRDM_POWER_RET)) @@ -282,6 +264,8 @@ static int omap3_enter_idle_bm(struct cpuidle_device *dev, if (per_next_state != per_saved_state) pwrdm_set_next_pwrst(per_pd, per_next_state); + new_state = next_valid_state(dev, state); + select_state: dev->last_state = new_state; ret = omap3_enter_idle(dev, new_state); @@ -295,31 +279,6 @@ select_state: DEFINE_PER_CPU(struct cpuidle_device, omap3_idle_dev); -/** - * omap3_cpuidle_update_states() - Update the cpuidle states - * @mpu_deepest_state: Enable states up to and including this for mpu domain - * @core_deepest_state: Enable states up to and including this for core domain - * - * This goes through the list of states available and enables and disables the - * validity of C states based on deepest state that can be achieved for the - * variable domain - */ -void omap3_cpuidle_update_states(u32 mpu_deepest_state, u32 core_deepest_state) -{ - int i; - - for (i = OMAP3_STATE_C1; i < OMAP3_MAX_STATES; i++) { - struct omap3_processor_cx *cx = &omap3_power_states[i]; - - if ((cx->mpu_state >= mpu_deepest_state) && - (cx->core_state >= core_deepest_state)) { - cx->valid = 1; - } else { - cx->valid = 0; - } - } -} - void omap3_pm_init_cpuidle(struct cpuidle_params *cpuidle_board_params) { int i; @@ -327,212 +286,109 @@ void omap3_pm_init_cpuidle(struct cpuidle_params *cpuidle_board_params) if (!cpuidle_board_params) return; - for (i = OMAP3_STATE_C1; i < OMAP3_MAX_STATES; i++) { - cpuidle_params_table[i].valid = - cpuidle_board_params[i].valid; - cpuidle_params_table[i].sleep_latency = - cpuidle_board_params[i].sleep_latency; - cpuidle_params_table[i].wake_latency = - cpuidle_board_params[i].wake_latency; - cpuidle_params_table[i].threshold = - cpuidle_board_params[i].threshold; + for (i = 0; i < OMAP3_NUM_STATES; i++) { + cpuidle_params_table[i].valid = cpuidle_board_params[i].valid; + cpuidle_params_table[i].exit_latency = + cpuidle_board_params[i].exit_latency; + cpuidle_params_table[i].target_residency = + cpuidle_board_params[i].target_residency; } return; } -/* omap3_init_power_states - Initialises the OMAP3 specific C states. - * - * Below is the desciption of each C state. - * C1 . MPU WFI + Core active - * C2 . MPU WFI + Core inactive - * C3 . MPU CSWR + Core inactive - * C4 . MPU OFF + Core inactive - * C5 . MPU CSWR + Core CSWR - * C6 . MPU OFF + Core CSWR - * C7 . MPU OFF + Core OFF - */ -void omap_init_power_states(void) -{ - /* C1 . MPU WFI + Core active */ - omap3_power_states[OMAP3_STATE_C1].valid = - cpuidle_params_table[OMAP3_STATE_C1].valid; - omap3_power_states[OMAP3_STATE_C1].type = OMAP3_STATE_C1; - omap3_power_states[OMAP3_STATE_C1].sleep_latency = - cpuidle_params_table[OMAP3_STATE_C1].sleep_latency; - omap3_power_states[OMAP3_STATE_C1].wakeup_latency = - cpuidle_params_table[OMAP3_STATE_C1].wake_latency; - omap3_power_states[OMAP3_STATE_C1].threshold = - cpuidle_params_table[OMAP3_STATE_C1].threshold; - omap3_power_states[OMAP3_STATE_C1].mpu_state = PWRDM_POWER_ON; - omap3_power_states[OMAP3_STATE_C1].core_state = PWRDM_POWER_ON; - omap3_power_states[OMAP3_STATE_C1].flags = CPUIDLE_FLAG_TIME_VALID; - omap3_power_states[OMAP3_STATE_C1].desc = "MPU ON + CORE ON"; - - /* C2 . MPU WFI + Core inactive */ - omap3_power_states[OMAP3_STATE_C2].valid = - cpuidle_params_table[OMAP3_STATE_C2].valid; - omap3_power_states[OMAP3_STATE_C2].type = OMAP3_STATE_C2; - omap3_power_states[OMAP3_STATE_C2].sleep_latency = - cpuidle_params_table[OMAP3_STATE_C2].sleep_latency; - omap3_power_states[OMAP3_STATE_C2].wakeup_latency = - cpuidle_params_table[OMAP3_STATE_C2].wake_latency; - omap3_power_states[OMAP3_STATE_C2].threshold = - cpuidle_params_table[OMAP3_STATE_C2].threshold; - omap3_power_states[OMAP3_STATE_C2].mpu_state = PWRDM_POWER_ON; - omap3_power_states[OMAP3_STATE_C2].core_state = PWRDM_POWER_ON; - omap3_power_states[OMAP3_STATE_C2].flags = CPUIDLE_FLAG_TIME_VALID | - CPUIDLE_FLAG_CHECK_BM; - omap3_power_states[OMAP3_STATE_C2].desc = "MPU ON + CORE ON"; - - /* C3 . MPU CSWR + Core inactive */ - omap3_power_states[OMAP3_STATE_C3].valid = - cpuidle_params_table[OMAP3_STATE_C3].valid; - omap3_power_states[OMAP3_STATE_C3].type = OMAP3_STATE_C3; - omap3_power_states[OMAP3_STATE_C3].sleep_latency = - cpuidle_params_table[OMAP3_STATE_C3].sleep_latency; - omap3_power_states[OMAP3_STATE_C3].wakeup_latency = - cpuidle_params_table[OMAP3_STATE_C3].wake_latency; - omap3_power_states[OMAP3_STATE_C3].threshold = - cpuidle_params_table[OMAP3_STATE_C3].threshold; - omap3_power_states[OMAP3_STATE_C3].mpu_state = PWRDM_POWER_RET; - omap3_power_states[OMAP3_STATE_C3].core_state = PWRDM_POWER_ON; - omap3_power_states[OMAP3_STATE_C3].flags = CPUIDLE_FLAG_TIME_VALID | - CPUIDLE_FLAG_CHECK_BM; - omap3_power_states[OMAP3_STATE_C3].desc = "MPU RET + CORE ON"; - - /* C4 . MPU OFF + Core inactive */ - omap3_power_states[OMAP3_STATE_C4].valid = - cpuidle_params_table[OMAP3_STATE_C4].valid; - omap3_power_states[OMAP3_STATE_C4].type = OMAP3_STATE_C4; - omap3_power_states[OMAP3_STATE_C4].sleep_latency = - cpuidle_params_table[OMAP3_STATE_C4].sleep_latency; - omap3_power_states[OMAP3_STATE_C4].wakeup_latency = - cpuidle_params_table[OMAP3_STATE_C4].wake_latency; - omap3_power_states[OMAP3_STATE_C4].threshold = - cpuidle_params_table[OMAP3_STATE_C4].threshold; - omap3_power_states[OMAP3_STATE_C4].mpu_state = PWRDM_POWER_OFF; - omap3_power_states[OMAP3_STATE_C4].core_state = PWRDM_POWER_ON; - omap3_power_states[OMAP3_STATE_C4].flags = CPUIDLE_FLAG_TIME_VALID | - CPUIDLE_FLAG_CHECK_BM; - omap3_power_states[OMAP3_STATE_C4].desc = "MPU OFF + CORE ON"; - - /* C5 . MPU CSWR + Core CSWR*/ - omap3_power_states[OMAP3_STATE_C5].valid = - cpuidle_params_table[OMAP3_STATE_C5].valid; - omap3_power_states[OMAP3_STATE_C5].type = OMAP3_STATE_C5; - omap3_power_states[OMAP3_STATE_C5].sleep_latency = - cpuidle_params_table[OMAP3_STATE_C5].sleep_latency; - omap3_power_states[OMAP3_STATE_C5].wakeup_latency = - cpuidle_params_table[OMAP3_STATE_C5].wake_latency; - omap3_power_states[OMAP3_STATE_C5].threshold = - cpuidle_params_table[OMAP3_STATE_C5].threshold; - omap3_power_states[OMAP3_STATE_C5].mpu_state = PWRDM_POWER_RET; - omap3_power_states[OMAP3_STATE_C5].core_state = PWRDM_POWER_RET; - omap3_power_states[OMAP3_STATE_C5].flags = CPUIDLE_FLAG_TIME_VALID | - CPUIDLE_FLAG_CHECK_BM; - omap3_power_states[OMAP3_STATE_C5].desc = "MPU RET + CORE RET"; - - /* C6 . MPU OFF + Core CSWR */ - omap3_power_states[OMAP3_STATE_C6].valid = - cpuidle_params_table[OMAP3_STATE_C6].valid; - omap3_power_states[OMAP3_STATE_C6].type = OMAP3_STATE_C6; - omap3_power_states[OMAP3_STATE_C6].sleep_latency = - cpuidle_params_table[OMAP3_STATE_C6].sleep_latency; - omap3_power_states[OMAP3_STATE_C6].wakeup_latency = - cpuidle_params_table[OMAP3_STATE_C6].wake_latency; - omap3_power_states[OMAP3_STATE_C6].threshold = - cpuidle_params_table[OMAP3_STATE_C6].threshold; - omap3_power_states[OMAP3_STATE_C6].mpu_state = PWRDM_POWER_OFF; - omap3_power_states[OMAP3_STATE_C6].core_state = PWRDM_POWER_RET; - omap3_power_states[OMAP3_STATE_C6].flags = CPUIDLE_FLAG_TIME_VALID | - CPUIDLE_FLAG_CHECK_BM; - omap3_power_states[OMAP3_STATE_C6].desc = "MPU OFF + CORE RET"; - - /* C7 . MPU OFF + Core OFF */ - omap3_power_states[OMAP3_STATE_C7].valid = - cpuidle_params_table[OMAP3_STATE_C7].valid; - omap3_power_states[OMAP3_STATE_C7].type = OMAP3_STATE_C7; - omap3_power_states[OMAP3_STATE_C7].sleep_latency = - cpuidle_params_table[OMAP3_STATE_C7].sleep_latency; - omap3_power_states[OMAP3_STATE_C7].wakeup_latency = - cpuidle_params_table[OMAP3_STATE_C7].wake_latency; - omap3_power_states[OMAP3_STATE_C7].threshold = - cpuidle_params_table[OMAP3_STATE_C7].threshold; - omap3_power_states[OMAP3_STATE_C7].mpu_state = PWRDM_POWER_OFF; - omap3_power_states[OMAP3_STATE_C7].core_state = PWRDM_POWER_OFF; - omap3_power_states[OMAP3_STATE_C7].flags = CPUIDLE_FLAG_TIME_VALID | - CPUIDLE_FLAG_CHECK_BM; - omap3_power_states[OMAP3_STATE_C7].desc = "MPU OFF + CORE OFF"; - - /* - * Erratum i583: implementation for ES rev < Es1.2 on 3630. We cannot - * enable OFF mode in a stable form for previous revisions. - * we disable C7 state as a result. - */ - if (IS_PM34XX_ERRATUM(PM_SDRC_WAKEUP_ERRATUM_i583)) { - omap3_power_states[OMAP3_STATE_C7].valid = 0; - cpuidle_params_table[OMAP3_STATE_C7].valid = 0; - pr_warn("%s: core off state C7 disabled due to i583\n", - __func__); - } -} - struct cpuidle_driver omap3_idle_driver = { .name = "omap3_idle", .owner = THIS_MODULE, }; +/* Helper to fill the C-state common data and register the driver_data */ +static inline struct omap3_idle_statedata *_fill_cstate( + struct cpuidle_device *dev, + int idx, const char *descr) +{ + struct omap3_idle_statedata *cx = &omap3_idle_data[idx]; + struct cpuidle_state *state = &dev->states[idx]; + + state->exit_latency = cpuidle_params_table[idx].exit_latency; + state->target_residency = cpuidle_params_table[idx].target_residency; + state->flags = CPUIDLE_FLAG_TIME_VALID; + state->enter = omap3_enter_idle_bm; + cx->valid = cpuidle_params_table[idx].valid; + sprintf(state->name, "C%d", idx + 1); + strncpy(state->desc, descr, CPUIDLE_DESC_LEN); + cpuidle_set_statedata(state, cx); + + return cx; +} + /** * omap3_idle_init - Init routine for OMAP3 idle * - * Registers the OMAP3 specific cpuidle driver with the cpuidle + * Registers the OMAP3 specific cpuidle driver to the cpuidle * framework with the valid set of states. */ int __init omap3_idle_init(void) { - int i, count = 0; - struct omap3_processor_cx *cx; - struct cpuidle_state *state; struct cpuidle_device *dev; + struct omap3_idle_statedata *cx; mpu_pd = pwrdm_lookup("mpu_pwrdm"); core_pd = pwrdm_lookup("core_pwrdm"); per_pd = pwrdm_lookup("per_pwrdm"); cam_pd = pwrdm_lookup("cam_pwrdm"); - omap_init_power_states(); cpuidle_register_driver(&omap3_idle_driver); - dev = &per_cpu(omap3_idle_dev, smp_processor_id()); - for (i = OMAP3_STATE_C1; i < OMAP3_MAX_STATES; i++) { - cx = &omap3_power_states[i]; - state = &dev->states[count]; - - if (!cx->valid) - continue; - cpuidle_set_statedata(state, cx); - state->exit_latency = cx->sleep_latency + cx->wakeup_latency; - state->target_residency = cx->threshold; - state->flags = cx->flags; - state->enter = (state->flags & CPUIDLE_FLAG_CHECK_BM) ? - omap3_enter_idle_bm : omap3_enter_idle; - if (cx->type == OMAP3_STATE_C1) - dev->safe_state = state; - sprintf(state->name, "C%d", count+1); - strncpy(state->desc, cx->desc, CPUIDLE_DESC_LEN); - count++; - } + /* C1 . MPU WFI + Core active */ + cx = _fill_cstate(dev, 0, "MPU ON + CORE ON"); + (&dev->states[0])->enter = omap3_enter_idle; + dev->safe_state = &dev->states[0]; + cx->valid = 1; /* C1 is always valid */ + cx->mpu_state = PWRDM_POWER_ON; + cx->core_state = PWRDM_POWER_ON; - if (!count) - return -EINVAL; - dev->state_count = count; + /* C2 . MPU WFI + Core inactive */ + cx = _fill_cstate(dev, 1, "MPU ON + CORE ON"); + cx->mpu_state = PWRDM_POWER_ON; + cx->core_state = PWRDM_POWER_ON; + + /* C3 . MPU CSWR + Core inactive */ + cx = _fill_cstate(dev, 2, "MPU RET + CORE ON"); + cx->mpu_state = PWRDM_POWER_RET; + cx->core_state = PWRDM_POWER_ON; - if (enable_off_mode) - omap3_cpuidle_update_states(PWRDM_POWER_OFF, PWRDM_POWER_OFF); - else - omap3_cpuidle_update_states(PWRDM_POWER_RET, PWRDM_POWER_RET); + /* C4 . MPU OFF + Core inactive */ + cx = _fill_cstate(dev, 3, "MPU OFF + CORE ON"); + cx->mpu_state = PWRDM_POWER_OFF; + cx->core_state = PWRDM_POWER_ON; + + /* C5 . MPU RET + Core RET */ + cx = _fill_cstate(dev, 4, "MPU RET + CORE RET"); + cx->mpu_state = PWRDM_POWER_RET; + cx->core_state = PWRDM_POWER_RET; + + /* C6 . MPU OFF + Core RET */ + cx = _fill_cstate(dev, 5, "MPU OFF + CORE RET"); + cx->mpu_state = PWRDM_POWER_OFF; + cx->core_state = PWRDM_POWER_RET; + + /* C7 . MPU OFF + Core OFF */ + cx = _fill_cstate(dev, 6, "MPU OFF + CORE OFF"); + /* + * Erratum i583: implementation for ES rev < Es1.2 on 3630. We cannot + * enable OFF mode in a stable form for previous revisions. + * We disable C7 state as a result. + */ + if (IS_PM34XX_ERRATUM(PM_SDRC_WAKEUP_ERRATUM_i583)) { + cx->valid = 0; + pr_warn("%s: core off state C7 disabled due to i583\n", + __func__); + } + cx->mpu_state = PWRDM_POWER_OFF; + cx->core_state = PWRDM_POWER_OFF; + dev->state_count = OMAP3_NUM_STATES; if (cpuidle_register_device(dev)) { printk(KERN_ERR "%s: CPUidle register device failed\n", __func__); diff --git a/arch/arm/mach-omap2/cpuidle44xx.c b/arch/arm/mach-omap2/cpuidle44xx.c new file mode 100644 index 000000000000..997f788785fe --- /dev/null +++ b/arch/arm/mach-omap2/cpuidle44xx.c @@ -0,0 +1,381 @@ +/* + * OMAP4 CPU idle Routines + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Rajendra Nayak <rnayak@ti.com> + * Santosh Shilimkar <santosh.shilimkar@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. + */ + +#include <linux/sched.h> +#include <linux/cpuidle.h> +#include <linux/clockchips.h> +#include <linux/notifier.h> +#include <linux/cpu.h> + +#include <asm/proc-fns.h> + +#include <mach/omap4-common.h> + +#include <plat/gpio.h> + +#include "pm.h" +#include "prm.h" + +#ifdef CONFIG_CPU_IDLE + +#define OMAP4_MAX_STATES 4 + +#define CPUIDLE_FLAG_CHECK_BM 0x10000 /* use omap4_enter_idle_bm() */ + +/* C1 - CPU0 ON + + CPU1 ON + MPU ON + CORE ON */ +#define OMAP4_STATE_C1 0 +/* C2 - CPU0 ON + CPU1 OFF + MPU ON + CORE ON */ +#define OMAP4_STATE_C2 1 +/* C3 - CPU0 OFF + CPU1 OFF + MPU CSWR + CORE CSWR */ +#define OMAP4_STATE_C3 2 +/* C4 - CPU0 OFF + CPU1 OFF + MPU RET + CORE CSWR */ +#define OMAP4_STATE_C4 3 + +struct omap4_processor_cx { + u8 valid; + u8 type; + u32 exit_latency; + u32 target_residency; + u32 cpu0_state; + u32 mpu_state; + u32 mpu_logic_state; + u32 core_state; + u32 core_logic_state; + u32 flags; + const char *desc; +}; + +struct omap4_processor_cx omap4_power_states[OMAP4_MAX_STATES]; +static struct powerdomain *mpu_pd, *cpu1_pd, *core_pd; +static int needs_state_data_update; +static unsigned int state_flags = CPUIDLE_FLAG_IGNORE; + +/* + * FIXME: Full latency numbers needs to be updated as part of + * cpuidle CORE retention support. + * Currently only MPUSS latency numbers are added based on + * measurements done internally. The numbers for MPUSS are + * not board dependent and hence set directly here instead of + * passing it from board files. + */ +static struct cpuidle_params cpuidle_params_table[] = { + /* C1 - CPU0 ON + CPU1 ON + MPU ON + CORE ON */ + {.exit_latency = 2 + 2, .target_residency = 5, .valid = 1}, + /* C2 - CPU0 ON + CPU1 OFF + MPU ON + CORE ON */ + {.exit_latency = 140 + 160, .target_residency = 300, .valid = 1}, + /* C3 - CPU0 OFF + CPU1 OFF + MPU CSWR + CORE CSWR */ + {.exit_latency = 1516 + 3220, .target_residency = 15000, .valid = 1}, + /* C4 - CPU0 OFF + CPU1 OFF + MPU OSWR + CORE OSWR */ + {.exit_latency = 1644 + 3298, .target_residency = 39000, .valid = 0}, +}; + +static bool omap4_idle_bm_busy(void) +{ + if (!omap4_can_sleep()) + return true; + return false; +} + +/** + * omap4_prepare_idle - Update C-state parameters dynamically + * @dev: cpuidle device + * + * Called from the CPUidle framework to prepare the device + * for idle before before calling the governor's select function. + */ +static int omap4_prepare_idle(struct cpuidle_device *dev) +{ + int i, ret = 0; + + if (!needs_state_data_update) + return ret; + + /* + * Update the C-state flags based on CPU1 online + * or offline state. On OMAP4, the low power C-states + * are made available when only CPU1 is offline. + */ + for (i = OMAP4_STATE_C2; i < OMAP4_MAX_STATES; i++) + dev->states[i].flags = state_flags; + + return ret; +} + +/** + * omap4_enter_idle - Programs OMAP4 to enter the specified state + * @dev: cpuidle device + * @state: The target state to be programmed + * + * Called from the CPUidle framework to program the device to the + * specified low power state selected by the governor. + * Returns the amount of time spent in the low power state. + */ +static int omap4_enter_idle(struct cpuidle_device *dev, + struct cpuidle_state *state) +{ + struct omap4_processor_cx *cx = cpuidle_get_statedata(state); + struct timespec ts_preidle, ts_postidle, ts_idle; + u32 cpu1_state; + int cpu_id = smp_processor_id(); + + /* Used to keep track of the total time in idle */ + getnstimeofday(&ts_preidle); + + local_irq_disable(); + local_fiq_disable(); + + /* + * Continue to do only WFI on CPU0 till CPU1 hits OFF state. + * This is necessary to honour hardware recommondation + * of triggeing all the possible low power modes once CPU1 is + * out of coherency and in OFF mode. + * Update dev->last_state so that governor stats reflects right + * data. + */ + cpu1_state = pwrdm_read_pwrst(cpu1_pd); + if ((cpu1_state != PWRDM_POWER_OFF) || (!cx->valid)) { + dev->last_state = dev->safe_state; + cx = cpuidle_get_statedata(dev->safe_state); + } + + pwrdm_set_logic_retst(mpu_pd, cx->mpu_logic_state); + omap_set_pwrdm_state(mpu_pd, cx->mpu_state); + pwrdm_set_logic_retst(core_pd, cx->core_logic_state); + omap_set_pwrdm_state(core_pd, cx->core_state); + + if (cx->type > OMAP4_STATE_C1) + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &cpu_id); + + omap4_enter_sleep(dev->cpu, cx->cpu0_state); + + /* restore the MPU and CORE states to ON */ + omap_set_pwrdm_state(mpu_pd, PWRDM_POWER_ON); + omap_set_pwrdm_state(core_pd, PWRDM_POWER_ON); + if (cx->type > OMAP4_STATE_C1) + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &cpu_id); + + getnstimeofday(&ts_postidle); + ts_idle = timespec_sub(ts_postidle, ts_preidle); + + local_irq_enable(); + local_fiq_enable(); + + return ts_idle.tv_nsec / NSEC_PER_USEC + ts_idle.tv_sec * USEC_PER_SEC; +} + +/** + * omap4_enter_idle_bm - Checks for any bus activity + * @dev: cpuidle device + * @state: The target state to be programmed + * + * Used for C states with CPUIDLE_FLAG_CHECK_BM flag set. This + * function checks for any pending activity and then programs the + * device to the specified or a safer state. + */ +static int omap4_enter_idle_bm(struct cpuidle_device *dev, + struct cpuidle_state *state) +{ + if ((omap4_idle_bm_busy())) { + BUG_ON(!dev->safe_state); + state = dev->safe_state; + } + + dev->last_state = state; + return omap4_enter_idle(dev, state); +} + +DEFINE_PER_CPU(struct cpuidle_device, omap4_idle_dev); + +/** + * omap4_init_power_states - Initialises the OMAP4 specific C states. + * + * Below is the desciption of each C state. + * C1 : CPUx wfi + MPU inative + Core inactive + */ +void omap4_init_power_states(void) +{ + /* + * C1 - CPU0 ON + + CPU1 ON + MPU ON + CORE ON + */ + omap4_power_states[OMAP4_STATE_C1].valid = + cpuidle_params_table[OMAP4_STATE_C1].valid; + omap4_power_states[OMAP4_STATE_C1].type = OMAP4_STATE_C1; + omap4_power_states[OMAP4_STATE_C1].exit_latency= + cpuidle_params_table[OMAP4_STATE_C1].exit_latency; + omap4_power_states[OMAP4_STATE_C1].target_residency = + cpuidle_params_table[OMAP4_STATE_C1].target_residency; + omap4_power_states[OMAP4_STATE_C1].cpu0_state = PWRDM_POWER_ON; + omap4_power_states[OMAP4_STATE_C1].mpu_state = PWRDM_POWER_ON; + omap4_power_states[OMAP4_STATE_C1].mpu_logic_state = PWRDM_POWER_RET; + omap4_power_states[OMAP4_STATE_C1].core_state = PWRDM_POWER_ON; + omap4_power_states[OMAP4_STATE_C1].core_logic_state = PWRDM_POWER_RET; + omap4_power_states[OMAP4_STATE_C1].flags = CPUIDLE_FLAG_TIME_VALID; + omap4_power_states[OMAP4_STATE_C1].desc = "MPU ON + CORE ON"; + + /* + * C2 - CPU0 ON + CPU1 OFF + MPU ON + CORE ON + */ + omap4_power_states[OMAP4_STATE_C2].valid = + cpuidle_params_table[OMAP4_STATE_C2].valid; + omap4_power_states[OMAP4_STATE_C2].type = OMAP4_STATE_C2; + omap4_power_states[OMAP4_STATE_C2].exit_latency = + cpuidle_params_table[OMAP4_STATE_C2].exit_latency; + omap4_power_states[OMAP4_STATE_C2].target_residency = + cpuidle_params_table[OMAP4_STATE_C2].target_residency; + omap4_power_states[OMAP4_STATE_C2].cpu0_state = PWRDM_POWER_ON; + omap4_power_states[OMAP4_STATE_C2].mpu_state = PWRDM_POWER_ON; + omap4_power_states[OMAP4_STATE_C2].mpu_logic_state = PWRDM_POWER_RET; + omap4_power_states[OMAP4_STATE_C2].core_state = PWRDM_POWER_ON; + omap4_power_states[OMAP4_STATE_C2].core_logic_state = PWRDM_POWER_RET; + omap4_power_states[OMAP4_STATE_C2].flags = CPUIDLE_FLAG_TIME_VALID; + omap4_power_states[OMAP4_STATE_C2].desc = "MPU ON + CORE ON"; + + /* + * C3 - CPU0 OFF + CPU1 OFF + MPU CSWR + CORE CSWR + */ + omap4_power_states[OMAP4_STATE_C3].valid = + cpuidle_params_table[OMAP4_STATE_C3].valid; + omap4_power_states[OMAP4_STATE_C3].type = OMAP4_STATE_C3; + omap4_power_states[OMAP4_STATE_C3].exit_latency = + cpuidle_params_table[OMAP4_STATE_C3].exit_latency; + omap4_power_states[OMAP4_STATE_C3].target_residency = + cpuidle_params_table[OMAP4_STATE_C3].target_residency; + omap4_power_states[OMAP4_STATE_C3].cpu0_state = PWRDM_POWER_OFF; + omap4_power_states[OMAP4_STATE_C3].mpu_state = PWRDM_POWER_RET; + omap4_power_states[OMAP4_STATE_C3].mpu_logic_state = PWRDM_POWER_RET; + omap4_power_states[OMAP4_STATE_C3].core_state = PWRDM_POWER_RET; + omap4_power_states[OMAP4_STATE_C3].core_logic_state = PWRDM_POWER_RET; + omap4_power_states[OMAP4_STATE_C3].flags = CPUIDLE_FLAG_TIME_VALID | + CPUIDLE_FLAG_CHECK_BM; + omap4_power_states[OMAP4_STATE_C3].desc = "MPU CSWR + CORE CSWR"; + + /* + * C4 - CPU0 OFF + CPU1 OFF + MPU OFF + CORE CSWR + */ + omap4_power_states[OMAP4_STATE_C4].valid = + cpuidle_params_table[OMAP4_STATE_C4].valid; + omap4_power_states[OMAP4_STATE_C4].type = OMAP4_STATE_C4; + omap4_power_states[OMAP4_STATE_C4].exit_latency = + cpuidle_params_table[OMAP4_STATE_C4].exit_latency; + omap4_power_states[OMAP4_STATE_C4].target_residency = + cpuidle_params_table[OMAP4_STATE_C4].target_residency; + omap4_power_states[OMAP4_STATE_C4].cpu0_state = PWRDM_POWER_OFF; + omap4_power_states[OMAP4_STATE_C4].mpu_state = PWRDM_POWER_RET; + omap4_power_states[OMAP4_STATE_C4].mpu_logic_state = PWRDM_POWER_OFF; + omap4_power_states[OMAP4_STATE_C4].core_state = PWRDM_POWER_RET; + omap4_power_states[OMAP4_STATE_C4].core_logic_state = PWRDM_POWER_OFF; + omap4_power_states[OMAP4_STATE_C4].flags = CPUIDLE_FLAG_TIME_VALID | + CPUIDLE_FLAG_CHECK_BM; + omap4_power_states[OMAP4_STATE_C4].desc = "MPU OSWR + CORE OSWR"; + +} + +struct cpuidle_driver omap4_idle_driver = { + .name = "omap4_idle", + .owner = THIS_MODULE, +}; + +/* + * CPU hotplug notifier to update the C-states when + * CPU1 is offline or onine. While updating C-state flag, + * keep the cpuidle disabled. + */ +static int __cpuinit omap_cpu_hotplug_notify(struct notifier_block *self, + unsigned long action, void *unused) +{ + switch (action) { + case CPU_ONLINE: + disable_hlt(); + needs_state_data_update = 1; + state_flags = CPUIDLE_FLAG_IGNORE; + enable_hlt(); + break; + case CPU_DEAD: + disable_hlt(); + needs_state_data_update = 1; + state_flags = CPUIDLE_FLAG_TIME_VALID; + enable_hlt(); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block __refdata omap_ilde_hotplug_notifier = { + .notifier_call = omap_cpu_hotplug_notify, +}; + +/** + * omap4_idle_init - Init routine for OMAP4 idle + * + * Registers the OMAP4 specific cpuidle driver with the cpuidle + * framework with the valid set of states. + */ +int __init omap4_idle_init(void) +{ + int cpu_id = 0, i, count = 0, ret; + struct omap4_processor_cx *cx; + struct cpuidle_state *state; + struct cpuidle_device *dev; + + mpu_pd = pwrdm_lookup("mpu_pwrdm"); + cpu1_pd = pwrdm_lookup("cpu1_pwrdm"); + core_pd = pwrdm_lookup("core_pwrdm"); + + omap4_init_power_states(); + cpuidle_register_driver(&omap4_idle_driver); + + dev = &per_cpu(omap4_idle_dev, cpu_id); + dev->cpu = cpu_id; + count = 0; + for (i = OMAP4_STATE_C1; i < OMAP4_MAX_STATES; i++) { + cx = &omap4_power_states[i]; + state = &dev->states[count]; + + if (!cx->valid) + continue; + cpuidle_set_statedata(state, cx); + state->exit_latency = cx->exit_latency; + state->target_residency = cx->target_residency; + state->flags = cx->flags; + if (cx->type == OMAP4_STATE_C1) + dev->safe_state = state; + state->enter = (state->flags & CPUIDLE_FLAG_CHECK_BM) ? + omap4_enter_idle_bm : omap4_enter_idle; + + sprintf(state->name, "C%d", count+1); + strncpy(state->desc, cx->desc, CPUIDLE_DESC_LEN); + count++; + } + + if (!count) + return -EINVAL; + dev->state_count = count; + dev->prepare = omap4_prepare_idle; + + if (cpuidle_register_device(dev)) { + pr_err("%s: CPUidle register device failed\n", __func__); + return -EIO; + } + + ret = register_hotcpu_notifier(&omap_ilde_hotplug_notifier); + if (ret) + return ret; + + return 0; +} +#else +int __init omap4_idle_init(void) +{ + return 0; +} +#endif /* CONFIG_CPU_IDLE */ diff --git a/arch/arm/mach-omap2/dpll3xxx.c b/arch/arm/mach-omap2/dpll3xxx.c index 81d2f9fd7642..f77022be783d 100644 --- a/arch/arm/mach-omap2/dpll3xxx.c +++ b/arch/arm/mach-omap2/dpll3xxx.c @@ -34,8 +34,6 @@ #include "clock.h" #include "cm2xxx_3xxx.h" #include "cm-regbits-34xx.h" -#include "cm-regbits-44xx.h" -#include "cm1_44xx.h" /* CM_AUTOIDLE_PLL*.AUTO_* bit values */ #define DPLL_AUTOIDLE_DISABLE 0x0 @@ -313,42 +311,6 @@ static int omap3_noncore_dpll_program(struct clk *clk, u16 m, u8 n, u16 freqsel) __raw_writel(v, dd->control_reg); } - /* - * On OMAP4460, to obtain MPU DPLL frequency higher - * than 1GHz, DCC (Duty Cycle Correction) needs to - * be enabled. - * Also the interconnect frequency to EMIF should - * be switched between MPU clk divide by 4 (for - * frequencies higher than 920Mhz) and MPU clk divide - * by 2 (for frequencies lower than or equal to 920Mhz) - * Lastly the async bridge to ABE must be MPU clk divide - * by 8 for MPU clk > 748Mhz and MPU clk divide by 4 - * for lower frequencies. - * TODO: For now use a strcmp, but need to find a - * better way to identify the MPU dpll. - */ - if (cpu_is_omap446x() && !strcmp(clk->name, "dpll_mpu_ck")) { - /* DCC control */ - v = __raw_readl(dd->mult_div1_reg); - if (dd->last_rounded_rate > 1000000000) - v |= OMAP4460_DCC_EN_MASK; /* Enable DCC */ - else - v &= ~OMAP4460_DCC_EN_MASK; /* Disable DCC */ - __raw_writel(v, dd->mult_div1_reg); - - /* EMIF/ABE clock rate control */ - v = __raw_readl(OMAP4430_CM_MPU_MPU_CLKCTRL); - if (dd->last_rounded_rate > 920000000) - v |= OMAP4460_CLKSEL_EMIF_DIV_MODE_MASK; - else - v &= ~OMAP4460_CLKSEL_EMIF_DIV_MODE_MASK; - if (dd->last_rounded_rate > 748000000) - v |= OMAP4460_CLKSEL_ABE_DIV_MODE_MASK; - else - v &= ~OMAP4460_CLKSEL_ABE_DIV_MODE_MASK; - __raw_writel(v, OMAP4430_CM_MPU_MPU_CLKCTRL); - } - /* Set DPLL multiplier, divider */ v = __raw_readl(dd->mult_div1_reg); v &= ~(dd->mult_mask | dd->div1_mask); @@ -465,7 +427,6 @@ int omap3_noncore_dpll_set_rate(struct clk *clk, unsigned long rate) u16 freqsel = 0; struct dpll_data *dd; int ret; - unsigned long orig_rate = 0; if (!clk || !rate) return -EINVAL; @@ -493,19 +454,6 @@ int omap3_noncore_dpll_set_rate(struct clk *clk, unsigned long rate) if (!ret) new_parent = dd->clk_bypass; } else { - /* - * On 4460, the MPU clk for frequencies higher than 1Ghz - * is sourced from CLKOUTX2_M3, instead of CLKOUT_M2, while - * value of M3 is fixed to 1. Hence for frequencies higher - * than 1 Ghz, lock the DPLL at half the rate so the - * CLKOUTX2_M3 then matches the requested rate. - */ - if (cpu_is_omap446x() && !strcmp(clk->name, "dpll_mpu_ck") - && (rate > 1000000000)) { - orig_rate = rate; - rate = rate/2; - } - if (dd->last_rounded_rate != rate) omap2_dpll_round_rate(clk, rate); @@ -520,12 +468,6 @@ int omap3_noncore_dpll_set_rate(struct clk *clk, unsigned long rate) WARN_ON(1); } - /* Set the rate back to original for book keeping*/ - if (orig_rate) { - rate = orig_rate; - dd->last_rounded_rate = dd->last_rounded_rate * 2; - } - pr_debug("clock: %s: set rate: locking rate to %lu.\n", clk->name, rate); diff --git a/arch/arm/mach-omap2/dpll44xx.c b/arch/arm/mach-omap2/dpll44xx.c index 4e4da6160d05..aa6e51726b5a 100644 --- a/arch/arm/mach-omap2/dpll44xx.c +++ b/arch/arm/mach-omap2/dpll44xx.c @@ -19,8 +19,13 @@ #include <plat/clock.h> #include "clock.h" +#include "cm1_44xx.h" #include "cm-regbits-44xx.h" +#define OMAP_1GHz 1000000000 +#define OMAP_920MHz 920000000 +#define OMAP_748MHz 748000000 + /* Supported only on OMAP4 */ int omap4_dpllmx_gatectrl_read(struct clk *clk) { @@ -82,3 +87,110 @@ const struct clkops clkops_omap4_dpllmx_ops = { .deny_idle = omap4_dpllmx_deny_gatectrl, }; +int omap4460_mpu_dpll_set_rate(struct clk *clk, unsigned long rate) +{ + struct dpll_data *dd; + u32 v; + unsigned long dpll_rate; + + if (!clk || !rate || !clk->parent) + return -EINVAL; + + dd = clk->parent->dpll_data; + + if (!dd) + return -EINVAL; + + if (!clk->parent->set_rate) + return -EINVAL; + + /* + * On OMAP4460, to obtain MPU DPLL frequency higher + * than 1GHz, DCC (Duty Cycle Correction) needs to + * be enabled. + * And needs to be kept disabled for < 1 Ghz. + */ + dpll_rate = omap2_get_dpll_rate(clk->parent); + v = __raw_readl(dd->mult_div1_reg); + if (rate < OMAP_1GHz) { + if (rate == dpll_rate) + return 0; + /* If DCC is enabled, disable it */ + if (v & OMAP4460_DCC_EN_MASK) { + v &= ~OMAP4460_DCC_EN_MASK; + __raw_writel(v, dd->mult_div1_reg); + } + clk->parent->set_rate(clk->parent, rate); + } else { + if (rate == dpll_rate/2) + return 0; + v &= ~OMAP4460_DCC_COUNT_MAX_MASK; + v |= (5 << OMAP4460_DCC_COUNT_MAX_SHIFT); + v |= OMAP4460_DCC_EN_MASK; + __raw_writel(v, dd->mult_div1_reg); + /* + * On 4460, the MPU clk for frequencies higher than 1Ghz + * is sourced from CLKOUTX2_M3, instead of CLKOUT_M2, while + * value of M3 is fixed to 1. Hence for frequencies higher + * than 1 Ghz, lock the DPLL at half the rate so the + * CLKOUTX2_M3 then matches the requested rate. + */ + clk->parent->set_rate(clk->parent, rate/2); + } + + clk->rate = rate; + + /* + * The interconnect frequency to EMIF should + * be switched between MPU clk divide by 4 (for + * frequencies higher than 920Mhz) and MPU clk divide + * by 2 (for frequencies lower than or equal to 920Mhz) + * Also the async bridge to ABE must be MPU clk divide + * by 8 for MPU clk > 748Mhz and MPU clk divide by 4 + * for lower frequencies. + */ + v = __raw_readl(OMAP4430_CM_MPU_MPU_CLKCTRL); + if (rate > OMAP_920MHz) + v |= OMAP4460_CLKSEL_EMIF_DIV_MODE_MASK; + else + v &= ~OMAP4460_CLKSEL_EMIF_DIV_MODE_MASK; + + if (rate > OMAP_748MHz) + v |= OMAP4460_CLKSEL_ABE_DIV_MODE_MASK; + else + v &= ~OMAP4460_CLKSEL_ABE_DIV_MODE_MASK; + __raw_writel(v, OMAP4430_CM_MPU_MPU_CLKCTRL); + + return 0; +} + +long omap4460_mpu_dpll_round_rate(struct clk *clk, unsigned long rate) +{ + if (!clk || !rate || !clk->parent) + return -EINVAL; + + if (clk->parent->round_rate) + return clk->parent->round_rate(clk, rate); + else + return 0; +} + +unsigned long omap4460_mpu_dpll_recalc(struct clk *clk) +{ + struct dpll_data *dd; + u32 v; + + if (!clk || !clk->parent) + return -EINVAL; + + dd = clk->parent->dpll_data; + + if (!dd) + return -EINVAL; + + v = __raw_readl(dd->mult_div1_reg); + if (v & OMAP4460_DCC_EN_MASK) + return omap2_get_dpll_rate(clk->parent) * 2; + else + return omap2_get_dpll_rate(clk->parent); +} diff --git a/arch/arm/mach-omap2/dvfs.c b/arch/arm/mach-omap2/dvfs.c new file mode 100644 index 000000000000..5420eb9fbad0 --- /dev/null +++ b/arch/arm/mach-omap2/dvfs.c @@ -0,0 +1,987 @@ +/* + * OMAP3/OMAP4 DVFS Management Routines + * + * Author: Vishwanath BS <vishwanath.bs@ti.com> + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Vishwanath BS <vishwanath.bs@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. + */ + +#include <linux/err.h> +#include <linux/spinlock.h> +#include <linux/plist.h> +#include <linux/slab.h> +#include <linux/opp.h> +#include <linux/clk.h> +#include <plat/common.h> +#include <plat/omap_device.h> +#include <plat/omap_hwmod.h> +#include <plat/clock.h> +#include "dvfs.h" +#include "smartreflex.h" +#include "powerdomain.h" + +/** + * DOC: Introduction + * ================= + * DVFS is a technique that uses the optimal operating frequency and voltage to + * allow a task to be performed in the required amount of time. + * OMAP processors have voltage domains whose voltage can be scaled to + * various levels depending on which the operating frequencies of certain + * devices belonging to the domain will also need to be scaled. This voltage + * frequency tuple is known as Operating Performance Point (OPP). A device + * can have multiple OPP's. Also a voltage domain could be shared between + * multiple devices. Also there could be dependencies between various + * voltage domains for maintaining system performance like VDD<X> + * should be at voltage v1 when VDD<Y> is at voltage v2. + * + * The design of this framework takes into account all the above mentioned + * points. To summarize the basic design of DVFS framework:- + * + * 1. Have device opp tables for each device whose operating frequency can be + * scaled. This is easy now due to the existance of hwmod layer which + * allow storing of device specific info. The device opp tables contain + * the opp pairs (frequency voltage tuples), the voltage domain pointer + * to which the device belongs to, the device specific set_rate and + * get_rate API's which will do the actual scaling of the device frequency + * and retrieve the current device frequency. + * 2. Introduce use counting on a per VDD basis. This is to take care multiple + * requests to scale a VDD. The VDD will be scaled to the maximum of the + * voltages requested. + * 3. Keep track of all scalable devices belonging to a particular voltage + * domain the voltage layer. + * 4. Keep track of frequency requests for each of the device. This will enable + * to scale individual devices to different frequency (even w/o scaling + * voltage aka frequency throttling) + * 5. Generic dvfs API that can be called by anybody to scale a device opp. + * This API takes the device pointer and frequency to which the device + * needs to be scaled to. This API then internally finds out the voltage + * domain to which the device belongs to and the voltage to which the voltage + * domain needs to be put to for the device to be scaled to the new frequency + * from he device opp table. Then this API will add requested frequency into + * the corresponding target device frequency list and add voltage request to + * the corresponding vdd. Subsequently it calls voltage scale function which + * will find out the highest requested voltage for the given vdd and scales + * the voltage to the required one. It also runs through the list of all + * scalable devices belonging to this voltage domain and scale them to the + * appropriate frequencies using the set_rate pointer in the device opp + * tables. + * 6. Handle inter VDD dependecies. + * + * + * DOC: The Core DVFS data structure: + * ================================== + * Structure Name Example Tree + * --------- + * /|\ +-------------------+ +-------------------+ + * | |User2 (dev2, freq2)+---\ |User4 (dev4, freq4)+---\ + * | +-------------------+ | +-------------------+ | + * (struct omap_dev_user_list) | | + * | +-------------------+ | +-------------------+ | + * | |User1 (dev1, freq1)+---| |User3 (dev3, freq3)+---| + * \|/ +-------------------+ | +-------------------+ | + * --------- | | + * /|\ +------------+------+ +---------------+--+ + * | | DEV1 (dev, | | DEV2 (dev) | + * (struct omap_vdd_dev_list)|omap_dev_user_list)| |omap_dev_user_list| + * | +------------+------+ +--+---------------+ + * \|/ /|\ /-----+-------------+------> others.. + * --------- Frequency | + * /|\ +--+------------------+ + * | | VDD_n | + * | | (omap_vdd_dev_list, | + * (struct omap_vdd_dvfs_info)** | omap_vdd_user_list) | + * | +--+------------------+ + * | | (ROOT NODE: omap_dvfs_info_list) + * \|/ | + * --------- Voltage \---+-------------+----------> others.. + * /|\ \|/ +-------+----+ +-----+--------+ + * | | vdd_user2 | | vdd_user3 | + * (struct omap_vdd_user_list) | (dev, volt)| | (dev, volt) | + * \|/ +------------+ +--------------+ + * --------- + * Key: ** -> Root of the tree. + * NOTE: we use the priority to store the voltage/frequency + * + * For voltage dependency description, see: struct dependency: + * voltagedomain -> (description of the voltagedomain) + * omap_vdd_info -> (vdd information) + * omap_vdd_dep_info[]-> (stores array of depedency info) + * omap_vdd_dep_volt[] -> (stores array of maps) + * (main_volt -> dep_volt) (a singular map) + */ + +/* Macros to give idea about scaling directions */ +#define DVFS_VOLT_SCALE_DOWN 0 +#define DVFS_VOLT_SCALE_NONE 1 +#define DVFS_VOLT_SCALE_UP 2 + +/** + * struct omap_dev_user_list - Structure maitain userlist per devide + * @dev: The device requesting for a particular frequency + * @node: The list head entry + * + * Using this structure, user list (requesting dev * and frequency) for + * each device is maintained. This is how we can have different devices + * at different frequencies (to support frequency locking and throttling). + * Even if one of the devices in a given vdd has locked it's frequency, + * other's can still scale their frequency using this list. + * If no one has placed a frequency request for a device, then device is + * set to the frequency from it's opp table. + */ +struct omap_dev_user_list { + struct device *dev; + struct plist_node node; +}; + +/** + * struct omap_vdd_dev_list - Device list per vdd + * @dev: The device belonging to a particular vdd + * @node: The list head entry + * @freq_user_list: The list of users for vdd device + * @clk: frequency control clock for this dev + * @user_lock: The lock for plist manipulation + */ +struct omap_vdd_dev_list { + struct device *dev; + struct list_head node; + struct plist_head freq_user_list; + struct clk *clk; + spinlock_t user_lock; /* spinlock for plist */ +}; + +/** + * struct omap_vdd_user_list - The per vdd user list + * @dev: The device asking for the vdd to be set at a particular + * voltage + * @node: The list head entry + */ +struct omap_vdd_user_list { + struct device *dev; + struct plist_node node; +}; + +/** + * struct omap_vdd_dvfs_info - The per vdd dvfs info + * @node: list node for vdd_dvfs_info list + * @user_lock: spinlock for plist operations + * @vdd_user_list: The vdd user list + * @voltdm: Voltage domains for which dvfs info stored + * @dev_list: Device list maintained per domain + * + * This is a fundamental structure used to store all the required + * DVFS related information for a vdd. + */ +struct omap_vdd_dvfs_info { + struct list_head node; + + spinlock_t user_lock; /* spin lock */ + struct plist_head vdd_user_list; + struct voltagedomain *voltdm; + struct list_head dev_list; +}; + +static LIST_HEAD(omap_dvfs_info_list); +static DEFINE_MUTEX(omap_dvfs_lock); + +/* Dvfs scale helper function */ +static int _dvfs_scale(struct device *req_dev, struct device *target_dev, + struct omap_vdd_dvfs_info *tdvfs_info); + +/* Few search functions to traverse and find pointers of interest */ + +/** + * _dvfs_info_to_dev() - Locate the parent device associated to dvfs_info + * @dvfs_info: dvfs_info to search for + * + * Returns NULL on failure. + */ +static struct device *_dvfs_info_to_dev(struct omap_vdd_dvfs_info *dvfs_info) +{ + struct omap_vdd_dev_list *tmp_dev; + if (IS_ERR_OR_NULL(dvfs_info)) + return NULL; + if (list_empty(&dvfs_info->dev_list)) + return NULL; + tmp_dev = list_first_entry(&dvfs_info->dev_list, + struct omap_vdd_dev_list, node); + return tmp_dev->dev; +} + +/** + * _dev_to_dvfs_info() - Locate the dvfs_info for a device + * @dev: dev to search for + * + * Returns NULL on failure. + */ +static struct omap_vdd_dvfs_info *_dev_to_dvfs_info(struct device *dev) +{ + struct omap_vdd_dvfs_info *dvfs_info; + struct omap_vdd_dev_list *temp_dev; + + if (IS_ERR_OR_NULL(dev)) + return NULL; + + list_for_each_entry(dvfs_info, &omap_dvfs_info_list, node) { + list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) { + if (temp_dev->dev == dev) + return dvfs_info; + } + } + + return NULL; +} + +/** + * _voltdm_to_dvfs_info() - Locate a dvfs_info given a voltdm pointer + * @voltdm: voltdm to search for + * + * Returns NULL on failure. + */ +static +struct omap_vdd_dvfs_info *_voltdm_to_dvfs_info(struct voltagedomain *voltdm) +{ + struct omap_vdd_dvfs_info *dvfs_info; + + if (IS_ERR_OR_NULL(voltdm)) + return NULL; + + list_for_each_entry(dvfs_info, &omap_dvfs_info_list, node) { + if (dvfs_info->voltdm == voltdm) + return dvfs_info; + } + + return NULL; +} + +/** + * _volt_to_opp() - Find OPP corresponding to a given voltage + * @dev: device pointer associated with the OPP list + * @volt: voltage to search for in uV + * + * Searches for exact match in the OPP list and returns handle to the matching + * OPP if found, else returns ERR_PTR in case of error and should be handled + * using IS_ERR. If there are multiple opps with same voltage, it will return + * the first available entry. Return pointer should be checked against IS_ERR. + * + * NOTE: since this uses OPP functions, use under rcu_lock. This function also + * assumes that the cpufreq table and OPP table are in sync - any modifications + * to either should be synchronized. + */ +static struct opp *_volt_to_opp(struct device *dev, unsigned long volt) +{ + struct opp *opp = ERR_PTR(-ENODEV); + unsigned long f = 0; + + do { + opp = opp_find_freq_ceil(dev, &f); + if (IS_ERR(opp)) + break; + if (opp_get_voltage(opp) >= volt) + break; + f++; + } while (1); + + return opp; +} + +/* rest of the helper functions */ +/** + * _add_vdd_user() - Add a voltage request + * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd + * @dev: device making the request + * @volt: requested voltage in uV + * + * Adds the given device's voltage request into corresponding + * vdd's omap_vdd_dvfs_info user list (plist). This list is used + * to find the maximum voltage request for a given vdd. + * + * Returns 0 on success. + */ +static int _add_vdd_user(struct omap_vdd_dvfs_info *dvfs_info, + struct device *dev, unsigned long volt) +{ + struct omap_vdd_user_list *user = NULL, *temp_user; + + if (!dvfs_info || IS_ERR(dvfs_info)) { + dev_warn(dev, "%s: VDD specified does not exist!\n", __func__); + return -EINVAL; + } + + spin_lock(&dvfs_info->user_lock); + plist_for_each_entry(temp_user, &dvfs_info->vdd_user_list, node) { + if (temp_user->dev == dev) { + user = temp_user; + break; + } + } + + if (!user) { + user = kzalloc(sizeof(struct omap_vdd_user_list), GFP_ATOMIC); + if (!user) { + dev_err(dev, + "%s: Unable to creat a new user for vdd_%s\n", + __func__, dvfs_info->voltdm->name); + spin_unlock(&dvfs_info->user_lock); + return -ENOMEM; + } + user->dev = dev; + } else { + plist_del(&user->node, &dvfs_info->vdd_user_list); + } + + plist_node_init(&user->node, volt); + plist_add(&user->node, &dvfs_info->vdd_user_list); + + spin_unlock(&dvfs_info->user_lock); + return 0; +} + +/** + * _remove_vdd_user() - Remove a voltage request + * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd + * @dev: device making the request + * + * Removes the given device's voltage request from corresponding + * vdd's omap_vdd_dvfs_info user list (plist). + * + * Returns 0 on success. + */ +static int _remove_vdd_user(struct omap_vdd_dvfs_info *dvfs_info, + struct device *dev) +{ + struct omap_vdd_user_list *user = NULL, *temp_user; + int ret = 0; + + if (!dvfs_info || IS_ERR(dvfs_info)) { + dev_err(dev, "%s: VDD specified does not exist!\n", __func__); + return -EINVAL; + } + + spin_lock(&dvfs_info->user_lock); + plist_for_each_entry(temp_user, &dvfs_info->vdd_user_list, node) { + if (temp_user->dev == dev) { + user = temp_user; + break; + } + } + + if (user) + plist_del(&user->node, &dvfs_info->vdd_user_list); + else { + dev_err(dev, "%s: Unable to find the user for vdd_%s\n", + __func__, dvfs_info->voltdm->name); + ret = -ENOENT; + } + + spin_unlock(&dvfs_info->user_lock); + kfree(user); + + return ret; +} + +/** + * _add_freq_request() - Add a requested device frequency + * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd + * @req_dev: device making the request + * @target_dev: target device for which frequency request is being made + * @freq: target device frequency + * + * This adds a requested frequency into target device's frequency list. + * + * Returns 0 on success. + */ +static int _add_freq_request(struct omap_vdd_dvfs_info *dvfs_info, + struct device *req_dev, struct device *target_dev, unsigned long freq) +{ + struct omap_dev_user_list *dev_user = NULL, *tmp_user; + struct omap_vdd_dev_list *temp_dev; + + if (!dvfs_info || IS_ERR(dvfs_info)) { + dev_warn(target_dev, "%s: VDD specified does not exist!\n", + __func__); + return -EINVAL; + } + + list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) { + if (temp_dev->dev == target_dev) + break; + } + + if (temp_dev->dev != target_dev) { + dev_warn(target_dev, "%s: target_dev does not exist!\n", + __func__); + return -EINVAL; + } + + spin_lock(&temp_dev->user_lock); + plist_for_each_entry(tmp_user, &temp_dev->freq_user_list, node) { + if (tmp_user->dev == req_dev) { + dev_user = tmp_user; + break; + } + } + + if (!dev_user) { + dev_user = kzalloc(sizeof(struct omap_dev_user_list), + GFP_ATOMIC); + if (!dev_user) { + dev_err(target_dev, + "%s: Unable to creat a new user for vdd_%s\n", + __func__, dvfs_info->voltdm->name); + spin_unlock(&temp_dev->user_lock); + return -ENOMEM; + } + dev_user->dev = req_dev; + } else { + plist_del(&dev_user->node, &temp_dev->freq_user_list); + } + + plist_node_init(&dev_user->node, freq); + plist_add(&dev_user->node, &temp_dev->freq_user_list); + spin_unlock(&temp_dev->user_lock); + return 0; +} + +/** + * _remove_freq_request() - Remove the requested device frequency + * + * @dvfs_info: omap_vdd_dvfs_info pointer for the required vdd + * @req_dev: device removing the request + * @target_dev: target device from which frequency request is being removed + * + * This removes a requested frequency from target device's frequency list. + * + * Returns 0 on success. + */ +static int _remove_freq_request(struct omap_vdd_dvfs_info *dvfs_info, + struct device *req_dev, struct device *target_dev) +{ + struct omap_dev_user_list *dev_user = NULL, *tmp_user; + int ret = 0; + struct omap_vdd_dev_list *temp_dev; + + if (!dvfs_info || IS_ERR(dvfs_info)) { + dev_warn(target_dev, "%s: VDD specified does not exist!\n", + __func__); + return -EINVAL; + } + + + list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) { + if (temp_dev->dev == target_dev) + break; + } + + if (temp_dev->dev != target_dev) { + dev_warn(target_dev, "%s: target_dev does not exist!\n", + __func__); + return -EINVAL; + } + + spin_lock(&temp_dev->user_lock); + plist_for_each_entry(tmp_user, &temp_dev->freq_user_list, node) { + if (tmp_user->dev == req_dev) { + dev_user = tmp_user; + break; + } + } + + if (dev_user) { + plist_del(&dev_user->node, &temp_dev->freq_user_list); + } else { + dev_err(target_dev, + "%s: Unable to remove the user for vdd_%s\n", + __func__, dvfs_info->voltdm->name); + ret = -EINVAL; + } + + spin_unlock(&temp_dev->user_lock); + kfree(dev_user); + + return ret; +} + +/** + * _dep_scan_table() - Scan a dependency table and mark for scaling + * @dev: device requesting the dependency scan (req_dev) + * @dep_info: dependency information (contains the table) + * @main_volt: voltage dependency to search for + * + * This runs down the table provided to find the match for main_volt + * provided and sets up a scale request for the dependent domain + * for the dependent voltage. + * + * Returns 0 if all went well. + */ +static int _dep_scan_table(struct device *dev, + struct omap_vdd_dep_info *dep_info, unsigned long main_volt) +{ + struct omap_vdd_dep_volt *dep_table = dep_info->dep_table; + int i; + unsigned long dep_volt = 0; + + if (!dep_table) { + dev_err(dev, "%s: deptable not present for vdd%s\n", + __func__, dep_info->name); + return -EINVAL; + } + + /* Now scan through the the dep table for a match */ + for (i = 0; i < dep_info->nr_dep_entries; i++) { + if (dep_table[i].main_vdd_volt == main_volt) { + dep_volt = dep_table[i].dep_vdd_volt; + break; + } + } + if (!dep_volt) { + dev_warn(dev, "%s: %ld volt map missing in vdd_%s\n", + __func__, main_volt, dep_info->name); + return -EINVAL; + } + + /* populate voltdm if it is not present */ + if (!dep_info->_dep_voltdm) { + dep_info->_dep_voltdm = voltdm_lookup(dep_info->name); + if (!dep_info->_dep_voltdm) { + dev_warn(dev, "%s: unable to get vdm%s\n", + __func__, dep_info->name); + return -ENODEV; + } + } + + /* See if dep_volt is possible for the vdd*/ + i = _add_vdd_user(_voltdm_to_dvfs_info(dep_info->_dep_voltdm), + dev, dep_volt); + if (i) + dev_err(dev, "%s: Failed to add dep to domain %s volt=%ld\n", + __func__, dep_info->name, dep_volt); + return i; +} + +/** + * _dep_scan_domains() - Scan dependency domains for a device + * @dev: device requesting the scan + * @vdd: vdd_info corresponding to the device + * @main_volt: voltage to scan for + * + * Since each domain *may* have multiple dependent domains, we scan + * through each of the dependent domains and invoke _dep_scan_table to + * scan each table for dependent domain for dependency scaling. + * + * This assumes that the dependent domain information is NULL entry terminated. + * Returns 0 if all went well. + */ +static int _dep_scan_domains(struct device *dev, + struct omap_vdd_info *vdd, unsigned long main_volt) +{ + struct omap_vdd_dep_info *dep_info = vdd->dep_vdd_info; + int ret = 0, r; + + if (!dep_info) { + dev_dbg(dev, "%s: No dependent VDD\n", __func__); + return 0; + } + + /* First scan through the mydomain->dep_domain list */ + while (dep_info->nr_dep_entries) { + r = _dep_scan_table(dev, dep_info, main_volt); + /* Store last failed value */ + ret = (r) ? r : ret; + dep_info++; + } + + return ret; +} + +/** + * _dep_scale_domains() - Cause a scale of all dependent domains + * @req_dev: device requesting the scale + * @req_vdd: vdd_info corresponding to the requesting device. + * + * This walks through every dependent domain and triggers a scale + * It is assumed that the corresponding scale handling for the + * domain translates this to freq and voltage scale operations as + * needed. + * + * Note: This is uses _dvfs_scale and one should be careful not to + * create a circular depedency (e.g. vdd_mpu->vdd_core->vdd->mpu) + * which can create deadlocks. No protection is provided to prevent + * this condition and a tree organization is assumed. + * + * Returns 0 if all went fine. + */ +static int _dep_scale_domains(struct device *req_dev, + struct omap_vdd_info *req_vdd) +{ + struct omap_vdd_dep_info *dep_info = req_vdd->dep_vdd_info; + int ret = 0, r; + + if (!dep_info) { + dev_dbg(req_dev, "%s: No dependent VDD\n", __func__); + return 0; + } + + /* First scan through the mydomain->dep_domain list */ + while (dep_info->nr_dep_entries) { + struct voltagedomain *tvoltdm = dep_info->_dep_voltdm; + + r = 0; + /* Scale it only if I have a voltdm mapped up for the dep */ + if (tvoltdm) { + struct omap_vdd_dvfs_info *tdvfs_info; + struct device *target_dev; + tdvfs_info = _voltdm_to_dvfs_info(tvoltdm); + if (!tdvfs_info) { + dev_warn(req_dev, "%s: no dvfs_info\n", + __func__); + goto next; + } + target_dev = _dvfs_info_to_dev(tdvfs_info); + if (!target_dev) { + dev_warn(req_dev, "%s: no target_dev\n", + __func__); + goto next; + } + r = _dvfs_scale(req_dev, target_dev, tdvfs_info); +next: + if (r) + dev_err(req_dev, "%s: dvfs_scale to %s =%d\n", + __func__, dev_name(target_dev), r); + } + /* Store last failed value */ + ret = (r) ? r : ret; + dep_info++; + } + + return ret; +} + +/** + * _dvfs_scale() : Scale the devices associated with a voltage domain + * @req_dev: Device requesting the scale + * @target_dev: Device requesting to be scaled + * @tdvfs_info: omap_vdd_dvfs_info pointer for the target domain + * + * This runs through the list of devices associated with the + * voltage domain and scales the device rates to the one requested + * by the user or those corresponding to the new voltage of the + * voltage domain. Target voltage is the highest voltage in the vdd_user_list. + * + * Returns 0 on success else the error value. + */ +static int _dvfs_scale(struct device *req_dev, struct device *target_dev, + struct omap_vdd_dvfs_info *tdvfs_info) +{ + unsigned long curr_volt, new_volt; + int volt_scale_dir = DVFS_VOLT_SCALE_DOWN; + struct omap_vdd_dev_list *temp_dev; + struct plist_node *node; + int ret = 0; + struct voltagedomain *voltdm; + struct omap_vdd_info *vdd; + + voltdm = tdvfs_info->voltdm; + if (IS_ERR_OR_NULL(voltdm)) { + dev_err(target_dev, "%s: bad voltdm\n", __func__); + return -EINVAL; + } + vdd = voltdm->vdd; + + /* Find the highest voltage being requested */ + node = plist_last(&tdvfs_info->vdd_user_list); + new_volt = node->prio; + + curr_volt = omap_vp_get_curr_volt(voltdm); + if (!curr_volt) + curr_volt = omap_voltage_get_nom_volt(voltdm); + + /* Disable smartreflex module across voltage and frequency scaling */ + omap_sr_disable(voltdm); + + if (curr_volt == new_volt) { + volt_scale_dir = DVFS_VOLT_SCALE_NONE; + } else if (curr_volt < new_volt) { + ret = voltdm_scale(voltdm, new_volt); + if (ret) { + dev_err(target_dev, + "%s: Unable to scale the %s to %ld volt\n", + __func__, voltdm->name, new_volt); + goto out; + } + volt_scale_dir = DVFS_VOLT_SCALE_UP; + } + + /* if we fail scale for dependent domains, go back to prev state */ + ret = _dep_scan_domains(target_dev, vdd, new_volt); + if (ret) { + dev_err(target_dev, + "%s: Error in scan domains for vdd_%s\n", + __func__, voltdm->name); + goto fail; + } + + /* unless we are moving down, scale dependents before we shift freq */ + if (!(DVFS_VOLT_SCALE_DOWN == volt_scale_dir)) { + ret = _dep_scale_domains(target_dev, vdd); + if (ret) { + dev_err(target_dev, + "%s: Error(%d)scale dependent with %ld volt\n", + __func__, ret, new_volt); + goto fail; + } + } + + /* Move all devices in list to the required frequencies */ + list_for_each_entry(temp_dev, &tdvfs_info->dev_list, node) { + struct device *dev; + struct opp *opp; + unsigned long freq = 0; + int r; + + dev = temp_dev->dev; + if (!plist_head_empty(&temp_dev->freq_user_list)) { + node = plist_last(&temp_dev->freq_user_list); + freq = node->prio; + } else { + /* dep domain? we'd probably have a voltage request */ + rcu_read_lock(); + opp = _volt_to_opp(dev, new_volt); + if (!IS_ERR(opp)) + freq = opp_get_freq(opp); + rcu_read_unlock(); + if (!freq) + continue; + } + + if (freq == clk_get_rate(temp_dev->clk)) { + dev_dbg(dev, "%s: Already at the requested" + "rate %ld\n", __func__, freq); + continue; + } + + r = clk_set_rate(temp_dev->clk, freq); + if (r < 0) { + dev_err(dev, "%s: clk set rate frq=%ld failed(%d)\n", + __func__, freq, r); + ret = r; + } + } + + if (ret) + goto fail; + + if (DVFS_VOLT_SCALE_DOWN == volt_scale_dir) { + voltdm_scale(voltdm, new_volt); + _dep_scale_domains(target_dev, vdd); + } + + /* All clear.. go out gracefully */ + goto out; + +fail: + pr_warning("%s: domain%s: No clean recovery available! could be bad!\n", + __func__, voltdm->name); +out: + /* Re-enable Smartreflex module */ + omap_sr_enable(voltdm); + + return ret; +} + +/* Public functions */ + +/** + * omap_device_scale() - Set a new rate at which the device is to operate + * @req_dev: pointer to the device requesting the scaling. + * @target_dev: pointer to the device that is to be scaled + * @rate: the rnew rate for the device. + * + * This API gets the device opp table associated with this device and + * tries putting the device to the requested rate and the voltage domain + * associated with the device to the voltage corresponding to the + * requested rate. Since multiple devices can be assocciated with a + * voltage domain this API finds out the possible voltage the + * voltage domain can enter and then decides on the final device + * rate. + * + * Return 0 on success else the error value + */ +int omap_device_scale(struct device *req_dev, struct device *target_dev, + unsigned long rate) +{ + struct opp *opp; + unsigned long volt, freq = rate; + struct omap_vdd_dvfs_info *tdvfs_info; + struct platform_device *pdev; + struct omap_device *od; + int ret = 0; + + pdev = container_of(target_dev, struct platform_device, dev); + if (IS_ERR_OR_NULL(pdev)) { + pr_err("%s: pdev is null!\n", __func__); + return -EINVAL; + } + + od = container_of(pdev, struct omap_device, pdev); + if (IS_ERR_OR_NULL(od)) { + pr_err("%s: od is null!\n", __func__); + return -EINVAL; + } + + /* Lock me to ensure cross domain scaling is secure */ + mutex_lock(&omap_dvfs_lock); + + rcu_read_lock(); + opp = opp_find_freq_ceil(target_dev, &freq); + if (IS_ERR(opp)) { + rcu_read_unlock(); + dev_err(target_dev, "%s: Unable to find OPP for freq%ld\n", + __func__, rate); + ret = -ENODEV; + goto out; + } + volt = opp_get_voltage(opp); + rcu_read_unlock(); + + tdvfs_info = _dev_to_dvfs_info(target_dev); + if (IS_ERR_OR_NULL(tdvfs_info)) { + dev_err(target_dev, "%s: (req=%s) no vdd![f=%ld, v=%ld]\n", + __func__, dev_name(req_dev), freq, volt); + ret = -ENODEV; + goto out; + } + + ret = _add_freq_request(tdvfs_info, req_dev, target_dev, freq); + if (ret) { + dev_err(target_dev, "%s: freqadd(%s) failed %d[f=%ld, v=%ld]\n", + __func__, dev_name(req_dev), ret, freq, volt); + goto out; + } + + ret = _add_vdd_user(tdvfs_info, req_dev, volt); + if (ret) { + dev_err(target_dev, "%s: vddadd(%s) failed %d[f=%ld, v=%ld]\n", + __func__, dev_name(req_dev), ret, freq, volt); + _remove_freq_request(tdvfs_info, req_dev, + target_dev); + goto out; + } + + /* Do the actual scaling */ + ret = _dvfs_scale(req_dev, target_dev, tdvfs_info); + if (ret) { + dev_err(target_dev, "%s: scale by %s failed %d[f=%ld, v=%ld]\n", + __func__, dev_name(req_dev), ret, freq, volt); + _remove_freq_request(tdvfs_info, req_dev, + target_dev); + _remove_vdd_user(tdvfs_info, target_dev); + /* Fall through */ + } + /* Fall through */ +out: + mutex_unlock(&omap_dvfs_lock); + return ret; +} +EXPORT_SYMBOL(omap_device_scale); + +/** + * omap_dvfs_register_device - Add a parent device into dvfs managed list + * @dev: Device to be added + * @voltdm_name: Name of the voltage domain for the device + * @clk_name: Name of the clock for the device + * + * This function adds a given device into user_list of corresponding + * vdd's omap_vdd_dvfs_info strucure. This list is traversed to scale + * frequencies of all the devices on a given vdd. + * + * Returns 0 on success. + */ +int __init omap_dvfs_register_device(struct device *dev, char *voltdm_name, + char *clk_name) +{ + struct omap_vdd_dev_list *temp_dev; + struct omap_vdd_dvfs_info *dvfs_info; + struct clk *clk = NULL; + int ret = 0; + + if (!voltdm_name) { + dev_err(dev, "%s: Bad voltdm name!\n", __func__); + return -EINVAL; + } + if (!clk_name) { + dev_err(dev, "%s: Bad clk name!\n", __func__); + return -EINVAL; + } + + /* Lock me to secure structure changes */ + mutex_lock(&omap_dvfs_lock); + + dvfs_info = _dev_to_dvfs_info(dev); + if (!dvfs_info) { + dvfs_info = kzalloc(sizeof(struct omap_vdd_dvfs_info), + GFP_KERNEL); + if (!dvfs_info) { + dev_warn(dev, "%s: unable to alloc memory!\n", + __func__); + ret = -ENOMEM; + goto out; + } + dvfs_info->voltdm = voltdm_lookup(voltdm_name); + if (!dvfs_info->voltdm) { + dev_warn(dev, "%s: unable to find voltdm %s!\n", + __func__, voltdm_name); + kfree(dvfs_info); + ret = -EINVAL; + goto out; + } + + /* Init the plist */ + spin_lock_init(&dvfs_info->user_lock); + plist_head_init(&dvfs_info->vdd_user_list, + &dvfs_info->user_lock); + /* Init the device list */ + INIT_LIST_HEAD(&dvfs_info->dev_list); + + list_add(&dvfs_info->node, &omap_dvfs_info_list); + } + + /* If device already added, we dont need to do more.. */ + list_for_each_entry(temp_dev, &dvfs_info->dev_list, node) { + if (temp_dev->dev == dev) + goto out; + } + + temp_dev = kzalloc(sizeof(struct omap_vdd_dev_list), GFP_KERNEL); + if (!temp_dev) { + dev_err(dev, "%s: Unable to creat a new device for vdd_%s\n", + __func__, dvfs_info->voltdm->name); + ret = -ENOMEM; + goto out; + } + + clk = clk_get(dev, clk_name); + if (IS_ERR_OR_NULL(clk)) { + dev_warn(dev, "%s: Bad clk pointer!\n", __func__); + kfree(temp_dev); + ret = -EINVAL; + goto out; + } + + /* Initialize priority ordered list */ + spin_lock_init(&temp_dev->user_lock); + plist_head_init(&temp_dev->freq_user_list, &temp_dev->user_lock); + + temp_dev->dev = dev; + temp_dev->clk = clk; + list_add_tail(&temp_dev->node, &dvfs_info->dev_list); + + /* Fall through */ +out: + mutex_unlock(&omap_dvfs_lock); + return ret; +} diff --git a/arch/arm/mach-omap2/dvfs.h b/arch/arm/mach-omap2/dvfs.h new file mode 100644 index 000000000000..da85c6deb944 --- /dev/null +++ b/arch/arm/mach-omap2/dvfs.h @@ -0,0 +1,38 @@ +/* + * OMAP3/OMAP4 DVFS Management Routines + * + * Author: Vishwanath BS <vishwanath.bs@ti.com> + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Vishwanath BS <vishwanath.bs@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. + */ + +#ifndef __ARCH_ARM_MACH_OMAP2_DVFS_H +#define __ARCH_ARM_MACH_OMAP2_DVFS_H +#include <plat/omap_hwmod.h> +#include "voltage.h" + +#ifdef CONFIG_PM +int omap_dvfs_register_device(struct device *dev, char *voltdm_name, + char *clk_name); +int omap_device_scale(struct device *req_dev, struct device *target_dev, + unsigned long rate); +#else +static inline int omap_dvfs_register_device(struct omap_hwmod *oh, + struct device *dev) +static inline int omap_dvfs_register_device(struct device *dev, + char *voltdm_name, char *clk_name) +{ + return -EINVAL; +} +static inline int omap_device_scale(struct device *req_dev, + struct device *target_dev, unsigned long rate) +{ + return -EINVAL; +} +#endif +#endif diff --git a/arch/arm/mach-omap2/include/mach/omap-wakeupgen.h b/arch/arm/mach-omap2/include/mach/omap-wakeupgen.h new file mode 100644 index 000000000000..66f31c3adf8e --- /dev/null +++ b/arch/arm/mach-omap2/include/mach/omap-wakeupgen.h @@ -0,0 +1,41 @@ +/* + * OMAP WakeupGen header file + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Written by Santosh Shilimkar <santosh.shilimkar@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. + */ +#ifndef OMAP_ARCH_WAKEUPGEN_H +#define OMAP_ARCH_WAKEUPGEN_H + +#define OMAP_WKG_CONTROL_0 0x00 +#define OMAP_WKG_ENB_A_0 0x10 +#define OMAP_WKG_ENB_B_0 0x14 +#define OMAP_WKG_ENB_C_0 0x18 +#define OMAP_WKG_ENB_D_0 0x1c +#define OMAP_WKG_ENB_SECURE_A_0 0x20 +#define OMAP_WKG_ENB_SECURE_B_0 0x24 +#define OMAP_WKG_ENB_SECURE_C_0 0x28 +#define OMAP_WKG_ENB_SECURE_D_0 0x2c +#define OMAP_WKG_ENB_A_1 0x410 +#define OMAP_WKG_ENB_B_1 0x414 +#define OMAP_WKG_ENB_C_1 0x418 +#define OMAP_WKG_ENB_D_1 0x41c +#define OMAP_WKG_ENB_SECURE_A_1 0x420 +#define OMAP_WKG_ENB_SECURE_B_1 0x424 +#define OMAP_WKG_ENB_SECURE_C_1 0x428 +#define OMAP_WKG_ENB_SECURE_D_1 0x42c +#define OMAP_AUX_CORE_BOOT_0 0x800 +#define OMAP_AUX_CORE_BOOT_1 0x804 +#define OMAP_PTMSYNCREQ_MASK 0xc00 +#define OMAP_PTMSYNCREQ_EN 0xc04 +#define OMAP_TIMESTAMPCYCLELO 0xc08 +#define OMAP_TIMESTAMPCYCLEHI 0xc0c + +extern int __init omap_wakeupgen_init(void); +extern void omap_wakeupgen_irqmask_all(unsigned int cpu, unsigned int set); +extern void omap_wakeupgen_save(void); +#endif diff --git a/arch/arm/mach-omap2/include/mach/omap4-common.h b/arch/arm/mach-omap2/include/mach/omap4-common.h index de441c05a6a6..f9769d38cdcb 100644 --- a/arch/arm/mach-omap2/include/mach/omap4-common.h +++ b/arch/arm/mach-omap2/include/mach/omap4-common.h @@ -13,24 +13,92 @@ #ifndef OMAP_ARCH_OMAP4_COMMON_H #define OMAP_ARCH_OMAP4_COMMON_H +#include <asm/proc-fns.h> /* - * wfi used in low power code. Directly opcode is used instead - * of instruction to avoid mulit-omap build break + * Secure low power context save/restore API index */ -#ifdef CONFIG_THUMB2_KERNEL -#define do_wfi() __asm__ __volatile__ ("wfi" : : : "memory") -#else -#define do_wfi() \ - __asm__ __volatile__ (".word 0xe320f003" : : : "memory") -#endif +#define HAL_SAVESECURERAM_INDEX 0x1a +#define HAL_SAVEHW_INDEX 0x1b +#define HAL_SAVEALL_INDEX 0x1c +#define HAL_SAVEGIC_INDEX 0x1d +#define PPA_SERVICE_NS_SMP 0x25 +/* + * Secure HAL API flags + */ +#define FLAG_START_CRITICAL 0x4 +#define FLAG_IRQFIQ_MASK 0x3 +#define FLAG_IRQ_ENABLE 0x2 +#define FLAG_FIQ_ENABLE 0x1 +#define NO_FLAG 0x0 +#ifndef __ASSEMBLER__ #ifdef CONFIG_CACHE_L2X0 -extern void __iomem *l2cache_base; +extern void __iomem *omap4_get_l2cache_base(void); #endif -extern void __iomem *gic_dist_base_addr; +#ifdef CONFIG_SMP +extern void __iomem *omap4_get_scu_base(void); +#else +static inline void __iomem *omap4_get_scu_base(void) +{ + return NULL; +} +#endif +extern void __iomem *omap4_get_gic_dist_base(void); +extern void __iomem *omap4_get_gic_cpu_base(void); +extern void __iomem *omap4_get_sar_ram_base(void); +extern dma_addr_t omap4_secure_ram_phys; extern void __init gic_init_irq(void); +extern void gic_cpu_enable(void); +extern void gic_cpu_disable(void); +extern void gic_dist_enable(void); +extern void gic_dist_disable(void); extern void omap_smc1(u32 fn, u32 arg); +/* + * Read MPIDR: Multiprocessor affinity register + */ +static inline unsigned int hard_smp_processor_id(void) +{ + unsigned int cpunum; + + asm volatile ( + "mrc p15, 0, %0, c0, c0, 5\n" + : "=r" (cpunum)); + return cpunum &= 0x0f; +} + +#if defined(CONFIG_SMP) && defined(CONFIG_PM) +extern int omap4_mpuss_init(void); +extern int omap4_enter_lowpower(unsigned int cpu, unsigned int power_state); +extern void omap4_cpu_suspend(unsigned int cpu, unsigned int save_state); +extern void omap4_cpu_resume(void); +extern u32 omap_smc2(u32 id, u32 falg, u32 pargs); +extern u32 omap4_secure_dispatcher(u32 idx, u32 flag, u32 nargs, + u32 arg1, u32 arg2, u32 arg3, u32 arg4); +#else + +static inline int omap4_enter_lowpower(unsigned int cpu, + unsigned int power_state) +{ + cpu_do_idle(); + return 0; +} + +static inline int omap4_mpuss_init(void) +{ + return 0; +} + +static inline void omap4_cpu_suspend(unsigned int cpu, unsigned int save_state) +{ +} + +static inline void omap4_cpu_resume(void) +{ +} + #endif +#endif /* __ASSEMBLER__ */ +#endif /* OMAP_ARCH_OMAP4_COMMON_H */ diff --git a/arch/arm/mach-omap2/io.c b/arch/arm/mach-omap2/io.c index 441e79d043a7..b5c8e80ac10e 100644 --- a/arch/arm/mach-omap2/io.c +++ b/arch/arm/mach-omap2/io.c @@ -38,6 +38,7 @@ #include "io.h" #include <plat/omap-pm.h> +#include "voltage.h" #include "powerdomain.h" #include "clockdomain.h" @@ -355,18 +356,22 @@ void __init omap2_init_common_infrastructure(void) u8 postsetup_state; if (cpu_is_omap242x()) { + omap2xxx_voltagedomains_init(); omap2xxx_powerdomains_init(); omap2xxx_clockdomains_init(); omap2420_hwmod_init(); } else if (cpu_is_omap243x()) { + omap2xxx_voltagedomains_init(); omap2xxx_powerdomains_init(); omap2xxx_clockdomains_init(); omap2430_hwmod_init(); } else if (cpu_is_omap34xx()) { + omap3xxx_voltagedomains_init(); omap3xxx_powerdomains_init(); omap3xxx_clockdomains_init(); omap3xxx_hwmod_init(); } else if (cpu_is_omap44xx()) { + omap44xx_voltagedomains_init(); omap44xx_powerdomains_init(); omap44xx_clockdomains_init(); omap44xx_hwmod_init(); diff --git a/arch/arm/mach-omap2/omap-hotplug.c b/arch/arm/mach-omap2/omap-hotplug.c index 4976b9393e49..f69cd5cf0f5d 100644 --- a/arch/arm/mach-omap2/omap-hotplug.c +++ b/arch/arm/mach-omap2/omap-hotplug.c @@ -19,7 +19,13 @@ #include <linux/smp.h> #include <asm/cacheflush.h> +#include <asm/hardware/gic.h> + #include <mach/omap4-common.h> +#include <mach/omap-wakeupgen.h> + +#include "powerdomain.h" +#include "clockdomain.h" int platform_cpu_kill(unsigned int cpu) { @@ -32,6 +38,12 @@ int platform_cpu_kill(unsigned int cpu) */ void platform_cpu_die(unsigned int cpu) { + unsigned int this_cpu; + static struct clockdomain *cpu1_clkdm; + + if (!cpu1_clkdm) + cpu1_clkdm = clkdm_lookup("mpu1_clkdm"); + flush_cache_all(); dsb(); @@ -39,18 +51,26 @@ void platform_cpu_die(unsigned int cpu) * we're ready for shutdown now, so do it */ if (omap_modify_auxcoreboot0(0x0, 0x200) != 0x0) - printk(KERN_CRIT "Secure clear status failed\n"); + pr_err("Secure clear status failed\n"); for (;;) { /* - * Execute WFI + * Enter into low power state + * clear all interrupt wakeup sources */ - do_wfi(); - - if (omap_read_auxcoreboot0() == cpu) { + omap_wakeupgen_irqmask_all(cpu, 1); + gic_cpu_disable(); + omap4_enter_lowpower(cpu, PWRDM_POWER_OFF); + this_cpu = hard_smp_processor_id(); + if (omap_read_auxcoreboot0() == this_cpu) { /* * OK, proper wakeup, we're done */ + omap_wakeupgen_irqmask_all(this_cpu, 0); + gic_cpu_enable(); + + /* Restore clockdomain to hardware supervised */ + clkdm_allow_idle(cpu1_clkdm); break; } pr_debug("CPU%u: spurious wakeup call\n", cpu); diff --git a/arch/arm/mach-omap2/omap-smp.c b/arch/arm/mach-omap2/omap-smp.c index b66cfe8bc464..1230c4e2032e 100644 --- a/arch/arm/mach-omap2/omap-smp.c +++ b/arch/arm/mach-omap2/omap-smp.c @@ -25,11 +25,19 @@ #include <mach/hardware.h> #include <mach/omap4-common.h> +#include "clockdomain.h" + /* SCU base address */ static void __iomem *scu_base; static DEFINE_SPINLOCK(boot_lock); + +void __iomem *omap4_get_scu_base(void) +{ + return scu_base; +} + void __cpuinit platform_secondary_init(unsigned int cpu) { /* @@ -48,6 +56,8 @@ void __cpuinit platform_secondary_init(unsigned int cpu) int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle) { + static struct clockdomain *cpu1_clkdm; + static bool booted; /* * Set synchronisation state between this boot processor * and the secondary one @@ -63,7 +73,27 @@ int __cpuinit boot_secondary(unsigned int cpu, struct task_struct *idle) omap_modify_auxcoreboot0(0x200, 0xfffffdff); flush_cache_all(); smp_wmb(); - smp_cross_call(cpumask_of(cpu), 1); + + if (!cpu1_clkdm) + cpu1_clkdm = clkdm_lookup("mpu1_clkdm"); + + /* + * The SGI(Software Generated Interrupts) are not wakeup capable + * from low power states. This is known limitation on OMAP4 and + * needs to be worked around by using software forced clockdomain + * wake-up. To wakeup CPU1, CPU0 forces the CPU1 clockdomain to + * software force wakeup. After the wakeup, CPU1 restores its + * clockdomain hardware supervised mode. + * More details can be found in OMAP4430 TRM - Version J + * Section : + * 4.3.4.2 Power States of CPU0 and CPU1 + */ + if (booted) { + clkdm_wakeup(cpu1_clkdm); + } else { + dsb_sev(); + booted = true; + } /* * Now the secondary core is starting up let it run its diff --git a/arch/arm/mach-omap2/omap-wakeupgen.c b/arch/arm/mach-omap2/omap-wakeupgen.c new file mode 100644 index 000000000000..345a55c95189 --- /dev/null +++ b/arch/arm/mach-omap2/omap-wakeupgen.c @@ -0,0 +1,319 @@ +/* + * OMAP WakeupGen Source file + * + * The WakeupGen unit is responsible for generating wakeup event from the + * incoming interrupts and enable bits. The WakeupGen is implemented in MPU + * always-On power domain. The WakeupGen consists of two sub-units, one for + * each CPU and manages only SPI interrupts. Hardware requirements is that + * the GIC and WakeupGen should be kept in sync for proper operation. + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Written by Santosh Shilimkar <santosh.shilimkar@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. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/platform_device.h> + +#include <asm/hardware/gic.h> + +#include <mach/omap-wakeupgen.h> +#include <mach/omap4-common.h> + +#include "omap4-sar-layout.h" + +#define NR_BANKS 4 +#define MAX_IRQS 128 +#define WKG_MASK_ALL 0x00000000 +#define WKG_UNMASK_ALL 0xffffffff +#define CPU_ENA_OFFSET 0x400 +#define CPU0_ID 0x0 +#define CPU1_ID 0x1 + +/* WakeupGen Base addres */ +static void __iomem *wakeupgen_base; +static void __iomem *sar_base; +static DEFINE_PER_CPU(u32 [NR_BANKS], irqmasks); +static DEFINE_SPINLOCK(wakeupgen_lock); + +/* + * Static helper functions + */ + +static inline u32 wakeupgen_readl(u8 idx, u32 cpu) +{ + return __raw_readl(wakeupgen_base + OMAP_WKG_ENB_A_0 + + (cpu * CPU_ENA_OFFSET) + (idx * 4)); +} + +static inline void wakeupgen_writel(u32 val, u8 idx, u32 cpu) +{ + __raw_writel(val, wakeupgen_base + OMAP_WKG_ENB_A_0 + + (cpu * CPU_ENA_OFFSET) + (idx * 4)); +} + +static inline void sar_writel(u32 val, u32 offset, u8 idx) +{ + __raw_writel(val, sar_base + offset + (idx * 4)); +} + +static void _wakeupgen_set_all(unsigned int cpu, unsigned int reg) +{ + u8 i; + + for (i = 0; i < NR_BANKS; i++) + wakeupgen_writel(reg, i, cpu); +} + +static inline int _wakeupgen_get_irq_info(u32 irq, u32 *bit_posn, u8 *reg_index) +{ + unsigned int spi_irq; + + /* + * PPIs and SGIs are not supported + */ + if (irq < OMAP44XX_IRQ_GIC_START) + return -EINVAL; + + /* + * Subtract the GIC offset + */ + spi_irq = irq - OMAP44XX_IRQ_GIC_START; + if (spi_irq > MAX_IRQS) { + pr_err("omap wakeupGen: Invalid IRQ%d\n", irq); + return -EINVAL; + } + + /* + * Each wakeup gen register controls 32 + * interrupts. i.e 1 bit per SPI IRQ + */ + *reg_index = spi_irq >> 5; + *bit_posn = spi_irq %= 32; + + return 0; +} + +static void _wakeupgen_clear(unsigned int irq) +{ + unsigned int cpu = smp_processor_id(); + u32 val, bit_number; + u8 i; + + if (_wakeupgen_get_irq_info(irq, &bit_number, &i)) + return; + + val = wakeupgen_readl(i, cpu); + val &= ~BIT(bit_number); + wakeupgen_writel(val, i, cpu); +} + +static void _wakeupgen_set(unsigned int irq) +{ + unsigned int cpu = smp_processor_id(); + u32 val, bit_number; + u8 i; + + if (_wakeupgen_get_irq_info(irq, &bit_number, &i)) + return; + + val = wakeupgen_readl(i, cpu); + val |= BIT(bit_number); + wakeupgen_writel(val, i, cpu); +} + +static void _wakeupgen_save_masks(unsigned int cpu) +{ + u8 i; + + for (i = 0; i < NR_BANKS; i++) + per_cpu(irqmasks, cpu)[i] = wakeupgen_readl(i, cpu); +} + +static void _wakeupgen_restore_masks(unsigned int cpu) +{ + u8 i; + + for (i = 0; i < NR_BANKS; i++) + wakeupgen_writel(per_cpu(irqmasks, cpu)[i], i, cpu); +} + +/* + * Architecture specific Mask extensiom + */ +static void wakeupgen_mask(struct irq_data *d) +{ + spin_lock(&wakeupgen_lock); + _wakeupgen_clear(d->irq); + spin_unlock(&wakeupgen_lock); +} + +/* + * Architecture specific Unmask extensiom + */ +static void wakeupgen_unmask(struct irq_data *d) +{ + + spin_lock(&wakeupgen_lock); + _wakeupgen_set(d->irq); + spin_unlock(&wakeupgen_lock); +} + +#ifdef CONFIG_PM +/* + * Architecture specific set_wake extension + */ +static int wakeupgen_set_wake(struct irq_data *d, unsigned int on) +{ + spin_lock(&wakeupgen_lock); + if (on) + _wakeupgen_set(d->irq); + else + _wakeupgen_clear(d->irq); + spin_unlock(&wakeupgen_lock); + + return 0; +} + +#else +#define wakeupgen_set_wake NULL +#endif + +/** + * omap_wakeupgen_irqmask_all() - Mask or unmask interrupts + * @cpu - CPU ID + * @set - The IRQ register mask. + * 0 = Mask all interrupts on the 'cpu' + * 1 = Unmask all interrupts on the 'cpu' + * + * Ensure that the initial mask is maintained. This is faster than + * iterating through GIC rgeisters to arrive at the correct masks + */ +void omap_wakeupgen_irqmask_all(unsigned int cpu, unsigned int set) +{ + if (omap_rev() == OMAP4430_REV_ES1_0) + return; + + spin_lock(&wakeupgen_lock); + if (set) { + _wakeupgen_save_masks(cpu); + _wakeupgen_set_all(cpu, WKG_MASK_ALL); + } else { + _wakeupgen_set_all(cpu, WKG_UNMASK_ALL); + _wakeupgen_restore_masks(cpu); + } + spin_unlock(&wakeupgen_lock); +} + +/* + * Initialse the wakeupgen module + */ +int __init omap_wakeupgen_init(void) +{ + u8 i; + + /* Not supported on on OMAP4 ES1.0 silicon */ + if (omap_rev() == OMAP4430_REV_ES1_0) { + WARN(1, "WakeupGen: Not supported on OMAP4430 ES1.0\n"); + return -EPERM; + } + + /* Static mapping, never released */ + wakeupgen_base = ioremap(OMAP44XX_WKUPGEN_BASE, SZ_4K); + if (WARN_ON(!wakeupgen_base)) + return -ENODEV; + + /* Clear all IRQ bitmasks at wakeupGen level */ + for (i = 0; i < NR_BANKS; i++) { + wakeupgen_writel(0, i, CPU0_ID); + wakeupgen_writel(0, i, CPU1_ID); + } + + /* + * Override gic architecture specific fucntioms to add + * OMAP WakeupGen interrupt controller along with GIC + */ + gic_arch_extn.irq_mask = wakeupgen_mask; + gic_arch_extn.irq_unmask = wakeupgen_unmask; + gic_arch_extn.irq_set_wake = wakeupgen_set_wake; + + return 0; +} + +/** + * omap_wakeupgen_save() - WakeupGen context save function + * + * Save WakewupGen context in SAR BANK3. Restore is done by ROM code. + * WakeupGen IP is integrated along with GIC to manage the + * interrupt wakeups from CPU low power states. It's located in + * always ON power domain. It manages masking/unmasking of + * Shared peripheral interrupts(SPI).So the interrupt enable/disable + * control should be in sync and consistent at WakeupGen and GIC so + * that interrupts are not lost. Hence GIC and WakeupGen are saved + * and restored together. + + * During normal operation, WakeupGen delivers external interrupts + * directly to the GIC. When the CPU asserts StandbyWFI, indicating + * it wants to enter lowpower state, the Standby Controller checks + * with the WakeupGen unit using the idlereq/idleack handshake to make + * sure there is no incoming interrupts. + */ + +void omap_wakeupgen_save(void) +{ + u8 i; + u32 val; + + if (omap_rev() == OMAP4430_REV_ES1_0) + return; + + if (!sar_base) + sar_base = omap4_get_sar_ram_base(); + + for (i = 0; i < NR_BANKS; i++) { + /* Save the CPUx interrupt mask for IRQ 0 to 127 */ + val = wakeupgen_readl(i, 0); + sar_writel(val, WAKEUPGENENB_OFFSET_CPU0, i); + val = wakeupgen_readl(i, 1); + sar_writel(val, WAKEUPGENENB_OFFSET_CPU1, i); + + /* + * Disable the secure interrupts for CPUx. The restore + * code blindly restores secure and non-secure interrupt + * masks from SAR RAM. Secure interrupts are not suppose + * to be enabled from HLOS. So overwrite the SAR location + * so that the secure interrupt remains disabled. + */ + sar_writel(0x0, WAKEUPGENENB_SECURE_OFFSET_CPU0, i); + sar_writel(0x0, WAKEUPGENENB_SECURE_OFFSET_CPU1, i); + } + + /* Save AuxBoot* registers */ + val = __raw_readl(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); + __raw_writel(val, sar_base + AUXCOREBOOT0_OFFSET); + val = __raw_readl(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); + __raw_writel(val, sar_base + AUXCOREBOOT1_OFFSET); + + /* Save SyncReq generation logic */ + val = __raw_readl(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); + __raw_writel(val, sar_base + AUXCOREBOOT0_OFFSET); + val = __raw_readl(wakeupgen_base + OMAP_AUX_CORE_BOOT_0); + __raw_writel(val, sar_base + AUXCOREBOOT1_OFFSET); + + /* Save SyncReq generation logic */ + val = __raw_readl(wakeupgen_base + OMAP_PTMSYNCREQ_MASK); + __raw_writel(val, sar_base + PTMSYNCREQ_MASK_OFFSET); + val = __raw_readl(wakeupgen_base + OMAP_PTMSYNCREQ_EN); + __raw_writel(val, sar_base + PTMSYNCREQ_EN_OFFSET); + + /* Set the Backup Bit Mask status */ + val = __raw_readl(sar_base + SAR_BACKUP_STATUS_OFFSET); + val |= SAR_BACKUP_STATUS_WAKEUPGEN; + __raw_writel(val, sar_base + SAR_BACKUP_STATUS_OFFSET); +} diff --git a/arch/arm/mach-omap2/omap2plus-cpufreq.c b/arch/arm/mach-omap2/omap2plus-cpufreq.c new file mode 100644 index 000000000000..77efcb0797a4 --- /dev/null +++ b/arch/arm/mach-omap2/omap2plus-cpufreq.c @@ -0,0 +1,292 @@ +/* + * OMAP2PLUS cpufreq driver + * + * CPU frequency scaling for OMAP using OPP information + * + * Copyright (C) 2005 Nokia Corporation + * Written by Tony Lindgren <tony@atomide.com> + * + * Based on cpu-sa1110.c, Copyright (C) 2001 Russell King + * + * Copyright (C) 2007-2011 Texas Instruments, Inc. + * Updated to support OMAP3 + * 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. + */ +#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/io.h> +#include <linux/opp.h> +#include <linux/cpu.h> + +#include <asm/system.h> +#include <asm/smp_plat.h> +#include <asm/cpu.h> + +#include <plat/clock.h> +#include <plat/omap-pm.h> +#include <plat/common.h> + +#include <mach/hardware.h> + +#include "dvfs.h" + +static struct cpufreq_frequency_table *freq_table; +static atomic_t freq_table_users = ATOMIC_INIT(0); +static struct clk *mpu_clk; +static char *mpu_clk_name; +static struct device *mpu_dev; +static bool cpu_boot_ok; + +static int omap_verify_speed(struct cpufreq_policy *policy) +{ + if (!freq_table) + return -EINVAL; + return cpufreq_frequency_table_verify(policy, freq_table); +} + +static unsigned int omap_getspeed(unsigned int cpu) +{ + unsigned long rate; + + if (cpu >= NR_CPUS) + return 0; + + rate = clk_get_rate(mpu_clk) / 1000; + return rate; +} + +static int omap_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int i; + int ret = 0; + struct cpufreq_freqs freqs; + + /* Changes not allowed until all CPUs are online at boot */ + if (is_smp() && !cpu_boot_ok && num_online_cpus() < NR_CPUS) + return ret; + + if (!freq_table) { + dev_err(mpu_dev, "%s: cpu%d: no freq table!\n", __func__, + policy->cpu); + return -EINVAL; + } + + ret = cpufreq_frequency_table_target(policy, freq_table, target_freq, + relation, &i); + if (ret) { + dev_dbg(mpu_dev, "%s: cpu%d: no freq match for %d(ret=%d)\n", + __func__, policy->cpu, target_freq, ret); + return ret; + } + freqs.new = freq_table[i].frequency; + if (!freqs.new) { + dev_err(mpu_dev, "%s: cpu%d: no match for freq %d\n", __func__, + policy->cpu, target_freq); + return -EINVAL; + } + + freqs.old = omap_getspeed(policy->cpu); + freqs.cpu = policy->cpu; + + if (freqs.old == freqs.new && policy->cur == freqs.new) + return ret; + + if (!is_smp()) { + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + goto set_freq; + } + + /* notifiers */ + for_each_cpu(i, policy->cpus) { + freqs.cpu = i; + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + } + +set_freq: +#ifdef CONFIG_CPU_FREQ_DEBUG + pr_info("cpufreq-omap: transition: %u --> %u\n", freqs.old, freqs.new); +#endif + + ret = omap_device_scale(mpu_dev, mpu_dev, freqs.new * 1000); + + /* + * Generic CPUFREQ driver jiffy update is under !SMP. So jiffies + * won't get updated when UP machine cpufreq build with + * CONFIG_SMP enabled. Below code is added only to manage that + * scenario + */ + freqs.new = omap_getspeed(policy->cpu); + if (!is_smp()) { + loops_per_jiffy = + cpufreq_scale(loops_per_jiffy, freqs.old, freqs.new); + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + goto skip_lpj; + } + +#ifdef CONFIG_SMP + /* + * 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. + */ + for_each_cpu(i, policy->cpus) + per_cpu(cpu_data, i).loops_per_jiffy = + cpufreq_scale(per_cpu(cpu_data, i).loops_per_jiffy, + freqs.old, freqs.new); +#endif + + /* notifiers */ + for_each_cpu(i, policy->cpus) { + freqs.cpu = i; + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + } + +skip_lpj: + return ret; +} + +static inline void freq_table_free(void) +{ + if (atomic_dec_and_test(&freq_table_users)) + opp_free_cpufreq_table(mpu_dev, &freq_table); +} + +static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy) +{ + int result = 0; + static cpumask_var_t cpumask; + + mpu_clk = clk_get(NULL, mpu_clk_name); + if (IS_ERR(mpu_clk)) + return PTR_ERR(mpu_clk); + + if (policy->cpu >= NR_CPUS) { + result = -EINVAL; + goto fail_ck; + } + + policy->cur = policy->min = policy->max = omap_getspeed(policy->cpu); + + if (atomic_inc_return(&freq_table_users) == 1) + result = opp_init_cpufreq_table(mpu_dev, &freq_table); + + if (result) { + dev_err(mpu_dev, "%s: cpu%d: failed creating freq table[%d]\n", + __func__, policy->cpu, result); + goto fail_ck; + } + + result = cpufreq_frequency_table_cpuinfo(policy, freq_table); + if (!result) + cpufreq_frequency_table_get_attr(freq_table, policy->cpu); + else + goto fail_table; + + policy->min = policy->cpuinfo.min_freq; + policy->max = policy->cpuinfo.max_freq; + policy->cur = omap_getspeed(policy->cpu); + + /* + * On OMAP SMP 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_or(cpumask, cpumask_of(policy->cpu), cpumask); + cpumask_copy(policy->cpus, cpumask); + } + + /* FIXME: what's the actual transition time? */ + policy->cpuinfo.transition_latency = 300 * 1000; + + /* + * If we have onlined all CPUs OR if we are the last CPU to be onlined + * at boot, release our boot-time transition constraint. + */ + if (is_smp() && !cpu_boot_ok && num_online_cpus() >= NR_CPUS - 1) + cpu_boot_ok = true; + + return 0; + +fail_table: + freq_table_free(); +fail_ck: + clk_put(mpu_clk); + return result; +} + +static int omap_cpu_exit(struct cpufreq_policy *policy) +{ + freq_table_free(); + clk_put(mpu_clk); + return 0; +} + +static struct freq_attr *omap_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver omap_driver = { + .flags = CPUFREQ_STICKY, + .verify = omap_verify_speed, + .target = omap_target, + .get = omap_getspeed, + .init = omap_cpu_init, + .exit = omap_cpu_exit, + .name = "omap2plus", + .attr = omap_cpufreq_attr, +}; + +static int __init omap_cpufreq_init(void) +{ + if (cpu_is_omap24xx()) { + mpu_clk_name = "virt_prcm_set"; + pr_warning("%s: omap2 cpufreq needs fixing\n", __func__); + return -EINVAL; + } else if (cpu_is_omap34xx()) { + mpu_clk_name = "dpll1_ck"; + } else if (cpu_is_omap443x()) { + mpu_clk_name = "dpll_mpu_ck"; + } else if (cpu_is_omap446x()) { + mpu_clk_name = "virt_dpll_mpu_ck"; + } + + if (!mpu_clk_name) { + pr_err("%s: unsupported Silicon?\n", __func__); + return -EINVAL; + } + + mpu_dev = omap2_get_mpuss_device(); + if (!mpu_dev) { + pr_warning("%s: unable to get the mpu device\n", __func__); + return -EINVAL; + } + + return cpufreq_register_driver(&omap_driver); +} + +static void __exit omap_cpufreq_exit(void) +{ + cpufreq_unregister_driver(&omap_driver); +} + +MODULE_DESCRIPTION("cpufreq driver for OMAP2PLUS SOCs"); +MODULE_LICENSE("GPL"); +late_initcall(omap_cpufreq_init); +module_exit(omap_cpufreq_exit); diff --git a/arch/arm/mach-omap2/omap4-common.c b/arch/arm/mach-omap2/omap4-common.c index e1dbf3b7a089..c3e65f6213d7 100644 --- a/arch/arm/mach-omap2/omap4-common.c +++ b/arch/arm/mach-omap2/omap4-common.c @@ -18,34 +18,84 @@ #include <asm/hardware/gic.h> #include <asm/hardware/cache-l2x0.h> +#include <asm/cacheflush.h> #include <mach/hardware.h> #include <mach/omap4-common.h> +#include <mach/omap-wakeupgen.h> + +#include "omap4-sar-layout.h" +#include "clockdomain.h" #ifdef CONFIG_CACHE_L2X0 -void __iomem *l2cache_base; +#define L2X0_POR_OFFSET_VALUE 0x9 +static void __iomem *l2cache_base; #endif -void __iomem *gic_dist_base_addr; +static void __iomem *gic_dist_base_addr; +static void __iomem *gic_cpu_base; +static void __iomem *sar_ram_base; +static struct clockdomain *l4_secure_clkdm; +void __iomem *omap4_get_gic_dist_base(void) +{ + return gic_dist_base_addr; +} + +void __iomem *omap4_get_gic_cpu_base(void) +{ + return gic_cpu_base; +} void __init gic_init_irq(void) { - void __iomem *gic_cpu_base; /* Static mapping, never released */ gic_dist_base_addr = ioremap(OMAP44XX_GIC_DIST_BASE, SZ_4K); - BUG_ON(!gic_dist_base_addr); + if (WARN_ON(!gic_dist_base_addr)) + return; /* Static mapping, never released */ gic_cpu_base = ioremap(OMAP44XX_GIC_CPU_BASE, SZ_512); - BUG_ON(!gic_cpu_base); + if (WARN_ON(!gic_cpu_base)) + return; + + omap_wakeupgen_init(); gic_init(0, 29, gic_dist_base_addr, gic_cpu_base); } +/* + * FIXME: Remove this GIC APIs once common GIG library starts + * supporting it. + */ +void gic_cpu_enable(void) +{ + __raw_writel(0xf0, gic_cpu_base + GIC_CPU_PRIMASK); + __raw_writel(1, gic_cpu_base + GIC_CPU_CTRL); +} + +void gic_cpu_disable(void) +{ + __raw_writel(0, gic_cpu_base + GIC_CPU_CTRL); +} + +void gic_dist_enable(void) +{ + __raw_writel(0x1, gic_dist_base_addr + GIC_DIST_CTRL); +} +void gic_dist_disable(void) +{ + __raw_writel(0, gic_dist_base_addr + GIC_CPU_CTRL); +} + #ifdef CONFIG_CACHE_L2X0 +void __iomem *omap4_get_l2cache_base(void) +{ + return l2cache_base; +} + static void omap4_l2x0_disable(void) { /* Disable PL310 L2 Cache controller */ @@ -61,6 +111,8 @@ static void omap4_l2x0_set_debug(unsigned long val) static int __init omap_l2_cache_init(void) { u32 aux_ctrl = 0; + u32 por_ctrl = 0; + u32 lockdown = 0; /* * To avoid code running on other OMAPs in @@ -71,29 +123,48 @@ static int __init omap_l2_cache_init(void) /* Static mapping, never released */ l2cache_base = ioremap(OMAP44XX_L2CACHE_BASE, SZ_4K); - BUG_ON(!l2cache_base); + if (WARN_ON(!l2cache_base)) + return -ENODEV; /* * 16-way associativity, parity disabled * Way size - 32KB (es1.0) * Way size - 64KB (es2.0 +) */ - aux_ctrl = ((1 << L2X0_AUX_CTRL_ASSOCIATIVITY_SHIFT) | - (0x1 << 25) | - (0x1 << L2X0_AUX_CTRL_NS_LOCKDOWN_SHIFT) | - (0x1 << L2X0_AUX_CTRL_NS_INT_CTRL_SHIFT)); + aux_ctrl = readl_relaxed(l2cache_base + L2X0_AUX_CTRL); if (omap_rev() == OMAP4430_REV_ES1_0) { aux_ctrl |= 0x2 << L2X0_AUX_CTRL_WAY_SIZE_SHIFT; - } else { - aux_ctrl |= ((0x3 << L2X0_AUX_CTRL_WAY_SIZE_SHIFT) | - (1 << L2X0_AUX_CTRL_SHARE_OVERRIDE_SHIFT) | - (1 << L2X0_AUX_CTRL_DATA_PREFETCH_SHIFT) | - (1 << L2X0_AUX_CTRL_INSTR_PREFETCH_SHIFT) | - (1 << L2X0_AUX_CTRL_EARLY_BRESP_SHIFT)); + goto skip_aux_por_api; + } + + /* + * Drop instruction prefetch hint since it degrades the + * the performance. + */ + aux_ctrl |= ((0x3 << L2X0_AUX_CTRL_WAY_SIZE_SHIFT) | + (1 << L2X0_AUX_CTRL_SHARE_OVERRIDE_SHIFT) | + (1 << L2X0_AUX_CTRL_DATA_PREFETCH_SHIFT) | + (1 << L2X0_AUX_CTRL_EARLY_BRESP_SHIFT)); + + omap_smc1(0x109, aux_ctrl); + + /* Setup POR Control register */ + por_ctrl = readl_relaxed(l2cache_base + L2X0_PREFETCH_CTRL); + + /* + * Double linefill is available only on OMAP4460 L2X0. + * Undocumented bit 25 is set for better performance. + */ + if (cpu_is_omap446x()) + por_ctrl |= ((1 << L2X0_PREFETCH_DATA_PREFETCH_SHIFT) | + (1 << L2X0_PREFETCH_DOUBLE_LINEFILL_SHIFT) | + (1 << 25)); + + if (cpu_is_omap446x() || (omap_rev() >= OMAP4430_REV_ES2_2)) { + por_ctrl |= L2X0_POR_OFFSET_VALUE; + omap_smc1(0x113, por_ctrl); } - if (omap_rev() != OMAP4430_REV_ES1_0) - omap_smc1(0x109, aux_ctrl); if (cpu_is_omap446x()) { writel_relaxed(0xa5a5, l2cache_base + 0x900); @@ -102,6 +173,20 @@ static int __init omap_l2_cache_init(void) writel_relaxed(0xa5a5, l2cache_base + 0x90C); } + /* + * FIXME: Temporary WA for OMAP4460 stability issue. + * Lock-down specific L2 cache ways which makes effective + * L2 size as 512 KB instead of 1 MB + */ + if (cpu_is_omap446x()) { + lockdown = 0xa5a5; + writel_relaxed(lockdown, l2cache_base + L2X0_LOCKDOWN_WAY_D0); + writel_relaxed(lockdown, l2cache_base + L2X0_LOCKDOWN_WAY_D1); + writel_relaxed(lockdown, l2cache_base + L2X0_LOCKDOWN_WAY_I0); + writel_relaxed(lockdown, l2cache_base + L2X0_LOCKDOWN_WAY_I1); + } + +skip_aux_por_api: /* Enable PL310 L2 Cache controller */ omap_smc1(0x102, 0x1); @@ -118,3 +203,80 @@ static int __init omap_l2_cache_init(void) } early_initcall(omap_l2_cache_init); #endif + +void __iomem *omap4_get_sar_ram_base(void) +{ + return sar_ram_base; +} + +/* + * SAR RAM used to save and restore the HW + * context in low power modes + */ +static int __init omap4_sar_ram_init(void) +{ + /* + * To avoid code running on other OMAPs in + * multi-omap builds + */ + if (!cpu_is_omap44xx()) + return -ENODEV; + + /* Static mapping, never released */ + sar_ram_base = ioremap(OMAP44XX_SAR_RAM_BASE, SZ_8K); + if (WARN_ON(!sar_ram_base)) + return -ENODEV; + + l4_secure_clkdm = clkdm_lookup("l4_secure_clkdm"); + + return 0; +} +early_initcall(omap4_sar_ram_init); + + +/* + * omap4_sec_dispatcher: Routine to dispatch low power secure + * service routines + * + * @idx: The HAL API index + * @flag: The flag indicating criticality of operation + * @nargs: Number of valid arguments out of four. + * @arg1, arg2, arg3 args4: Parameters passed to secure API + * + * Return the error value on success/failure + */ +u32 omap4_secure_dispatcher(u32 idx, u32 flag, u32 nargs, u32 arg1, u32 arg2, + u32 arg3, u32 arg4) +{ + u32 ret; + u32 param[5]; + + param[0] = nargs; + param[1] = arg1; + param[2] = arg2; + param[3] = arg3; + param[4] = arg4; + + /* + * Put l4 secure to software wakeup so that secure + * modules are accessible + */ + clkdm_wakeup(l4_secure_clkdm); + + /* + * Secure API needs physical address + * pointer for the parameters + */ + flush_cache_all(); + outer_clean_range(__pa(param), __pa(param + 5)); + + ret = omap_smc2(idx, flag, __pa(param)); + + /* + * Restore l4 secure to hardware superwised to allow + * secure modules idle + */ + clkdm_allow_idle(l4_secure_clkdm); + + return ret; +} diff --git a/arch/arm/mach-omap2/omap4-mpuss-lowpower.c b/arch/arm/mach-omap2/omap4-mpuss-lowpower.c new file mode 100644 index 000000000000..bdaddd852692 --- /dev/null +++ b/arch/arm/mach-omap2/omap4-mpuss-lowpower.c @@ -0,0 +1,523 @@ +/* + * OMAP4 MPUSS low power code + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Written by Santosh Shilimkar <santosh.shilimkar@ti.com> + * + * OMAP4430 MPUSS mainly consists of dual Cortex-A9 with per-CPU + * Local timer and Watchdog, GIC, SCU, PL310 L2 cache controller, + * CPU0 and CPU1 LPRM modules. + * CPU0, CPU1 and MPUSS each have there own power domain and + * hence multiple low power combinations of MPUSS are possible. + * + * The CPU0 and CPU1 can't support Closed switch Retention (CSWR) + * because the mode is not supported by hw constraints of dormant + * mode. While waking up from the dormant mode, a reset signal + * to the Cortex-A9 processor must be asserted by the external + * power controller. + * + * With architectural inputs and hardware recommendations, only + * below modes are supported from power gain vs latency point of view. + * + * CPU0 CPU1 MPUSS + * ---------------------------------------------- + * ON ON ON + * ON(Inactive) OFF ON(Inactive) + * OFF OFF CSWR + * OFF OFF OSWR (*TBD) + * OFF OFF OFF + * ---------------------------------------------- + * + * Note: CPU0 is the master core and it is the last CPU to go down + * and first to wake-up when MPUSS low power states are excercised + * + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/errno.h> +#include <linux/linkage.h> +#include <linux/smp.h> + +#include <asm/cacheflush.h> +#include <linux/dma-mapping.h> + +#include <asm/tlbflush.h> +#include <asm/smp_scu.h> +#include <asm/system.h> +#include <asm/irq.h> +#include <asm/hardware/gic.h> +#include <asm/hardware/cache-l2x0.h> + +#include <plat/omap44xx.h> +#include <mach/omap4-common.h> +#include <mach/omap-wakeupgen.h> + +#include "omap4-sar-layout.h" +#include "pm.h" +#include "powerdomain.h" + +#ifdef CONFIG_SMP + +#define GIC_MASK_ALL 0x0 +#define GIC_ISR_NON_SECURE 0xffffffff +#define SPI_ENABLE_SET_OFFSET 0x04 +#define PPI_PRI_OFFSET 0x1c +#define SPI_PRI_OFFSET 0x20 +#define SPI_TARGET_OFFSET 0x20 +#define SPI_CONFIG_OFFSET 0x20 + +/* GIC save SAR bank base */ +static struct powerdomain *mpuss_pd; +/* + * Maximum Secure memory storage size. + */ +#define OMAP4_SECURE_RAM_STORAGE (88 * SZ_1K) +/* + * Physical address of secure memory storage + */ +dma_addr_t omap4_secure_ram_phys; +static void *secure_ram; + +/* Variables to store maximum spi(Shared Peripheral Interrupts) registers. */ +static u32 max_spi_irq, max_spi_reg; + +struct omap4_cpu_pm_info { + struct powerdomain *pwrdm; + void __iomem *scu_sar_addr; +}; + +static void __iomem *gic_dist_base; +static void __iomem *sar_base; + +static DEFINE_PER_CPU(struct omap4_cpu_pm_info, omap4_pm_info); + +/* Helper functions */ +static inline void sar_writel(u32 val, u32 offset, u8 idx) +{ + __raw_writel(val, sar_base + offset + 4 * idx); +} + +static inline u32 gic_readl(u32 offset, u8 idx) +{ + return __raw_readl(gic_dist_base + offset + 4 * idx); +} + +/* + * Set the CPUx powerdomain's previous power state + */ +static inline void set_cpu_next_pwrst(unsigned int cpu_id, + unsigned int power_state) +{ + struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id); + + pwrdm_set_next_pwrst(pm_info->pwrdm, power_state); +} + +/* + * Read CPU's previous power state + */ +static inline unsigned int read_cpu_prev_pwrst(unsigned int cpu_id) +{ + struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id); + + return pwrdm_read_prev_pwrst(pm_info->pwrdm); +} + +/* + * Clear the CPUx powerdomain's previous power state + */ +static inline void clear_cpu_prev_pwrst(unsigned int cpu_id) +{ + struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id); + + pwrdm_clear_all_prev_pwrst(pm_info->pwrdm); +} + +/* + * Store the SCU power status value to scratchpad memory + */ +static void scu_pwrst_prepare(unsigned int cpu_id, unsigned int cpu_state) +{ + struct omap4_cpu_pm_info *pm_info = &per_cpu(omap4_pm_info, cpu_id); + u32 scu_pwr_st, l1_state; + + switch (cpu_state) { + case PWRDM_POWER_RET: + scu_pwr_st = SCU_PM_DORMANT; + l1_state = 0x00; + break; + case PWRDM_POWER_OFF: + scu_pwr_st = SCU_PM_POWEROFF; + l1_state = 0xff; + break; + case PWRDM_POWER_ON: + case PWRDM_POWER_INACTIVE: + default: + scu_pwr_st = SCU_PM_NORMAL; + break; + } + + __raw_writel(scu_pwr_st, pm_info->scu_sar_addr); + if (omap_type() != OMAP2_DEVICE_TYPE_GP) + writel(l1_state, pm_info->scu_sar_addr + 0x04); +} + +/* + * Save GIC context in SAR RAM. Restore is done by ROM code + * GIC is lost only when MPU hits OSWR or OFF. It consists + * of a distributor and a per-CPU interface module. The GIC + * save restore is optimised to save only necessary registers. + */ +static void gic_save_context(void) +{ + u8 i; + u32 val; + + /* + * Interrupt Clear Enable registers are inverse of set enable + * and hence not needed to be saved. ROM code programs it + * based on Set Enable register values. + */ + + /* Save CPU 0 Interrupt Set Enable register */ + val = gic_readl(GIC_DIST_ENABLE_SET, 0); + sar_writel(val, ICDISER_CPU0_OFFSET, 0); + + /* Disable interrupts on CPU1 */ + sar_writel(GIC_MASK_ALL, ICDISER_CPU1_OFFSET, 0); + + /* Save all SPI Set Enable register */ + for (i = 0; i < max_spi_reg; i++) { + val = gic_readl(GIC_DIST_ENABLE_SET + SPI_ENABLE_SET_OFFSET, i); + sar_writel(val, ICDISER_SPI_OFFSET, i); + } + + /* + * Interrupt Priority Registers + * Secure sw accesses, last 5 bits of the 8 bits (bit[7:3] are used) + * Non-Secure sw accesses, last 4 bits (i.e. bits[7:4] are used) + * But the Secure Bits[7:3] are shifted by 1 in Non-Secure access. + * Secure (bits[7:3] << 1)== Non Secure bits[7:4] + * Hence right shift the value by 1 while saving the priority + */ + + /* Save SGI priority registers (Software Generated Interrupt) */ + for (i = 0; i < 4; i++) { + val = gic_readl(GIC_DIST_PRI, i); + + /* Save the priority bits of the Interrupts */ + sar_writel(val >> 0x1, ICDIPR_SFI_CPU0_OFFSET, i); + + /* Disable the interrupts on CPU1 */ + sar_writel(GIC_MASK_ALL, ICDIPR_SFI_CPU1_OFFSET, i); + } + + /* Save PPI priority registers (Private Peripheral Intterupts) */ + val = gic_readl(GIC_DIST_PRI + PPI_PRI_OFFSET, 0); + sar_writel(val >> 0x1, ICDIPR_PPI_CPU0_OFFSET, 0); + sar_writel(GIC_MASK_ALL, ICDIPR_PPI_CPU1_OFFSET, 0); + + /* SPI priority registers - 4 interrupts/register */ + for (i = 0; i < (max_spi_irq / 4); i++) { + val = gic_readl((GIC_DIST_PRI + SPI_PRI_OFFSET), i); + sar_writel(val >> 0x1, ICDIPR_SPI_OFFSET, i); + } + + /* SPI Interrupt Target registers - 4 interrupts/register */ + for (i = 0; i < (max_spi_irq / 4); i++) { + val = gic_readl((GIC_DIST_TARGET + SPI_TARGET_OFFSET), i); + sar_writel(val, ICDIPTR_SPI_OFFSET, i); + } + + /* SPI Interrupt Congigeration eegisters- 16 interrupts/register */ + for (i = 0; i < (max_spi_irq / 16); i++) { + val = gic_readl((GIC_DIST_CONFIG + SPI_CONFIG_OFFSET), i); + sar_writel(val, ICDICFR_OFFSET, i); + } + + /* Set the Backup Bit Mask status for GIC */ + val = __raw_readl(sar_base + SAR_BACKUP_STATUS_OFFSET); + val |= (SAR_BACKUP_STATUS_GIC_CPU0 | SAR_BACKUP_STATUS_GIC_CPU1); + __raw_writel(val, sar_base + SAR_BACKUP_STATUS_OFFSET); +} +/* + * API to save GIC and Wakeupgen using secure API + * for HS/EMU device + */ +static void save_gic_wakeupgen_secure(void) +{ + u32 ret; + ret = omap4_secure_dispatcher(HAL_SAVEGIC_INDEX, + FLAG_IRQFIQ_MASK | FLAG_START_CRITICAL, + 0, 0, 0, 0, 0); + if (!ret) + pr_debug("GIC and Wakeupgen context save failed\n"); +} + +/* + * API to save Secure RAM using secure API + * for HS/EMU device + */ +static void save_secure_ram(void) +{ + u32 ret; + ret = omap4_secure_dispatcher(HAL_SAVESECURERAM_INDEX, + FLAG_IRQFIQ_MASK | FLAG_START_CRITICAL, + 1, omap4_secure_ram_phys, 0, 0, 0); + if (!ret) + pr_debug("Secure ram context save failed\n"); +} + +/* + * OMAP4 MPUSS Low Power Entry Function + * + * The purpose of this function is to manage low power programming + * of OMAP4 MPUSS subsystem + * Paramenters: + * cpu : CPU ID + * power_state: Targetted Low power state. + * + * MPUSS Low power states + * The basic rule is that the MPUSS power domain must be at the higher or + * equal power state (state that consume more power) than the higher of the + * two CPUs. For example, it is illegal for system power to be OFF, while + * the power of one or both of the CPU is DORMANT. When an illegal state is + * entered, then the hardware behavior is unpredictable. + * + * MPUSS state for the context save + * save_state = + * 0 - Nothing lost and no need to save: MPUSS INACTIVE + * 1 - CPUx L1 and logic lost: MPUSS CSWR + * 2 - CPUx L1 and logic lost + GIC lost: MPUSS OSWR + * 3 - CPUx L1 and logic lost + GIC + L2 lost: MPUSS OFF + */ +int omap4_enter_lowpower(unsigned int cpu, unsigned int power_state) +{ + unsigned int save_state = 0; + unsigned int wakeup_cpu; + + if ((cpu >= NR_CPUS) || (omap_rev() == OMAP4430_REV_ES1_0)) + goto ret; + + switch (power_state) { + case PWRDM_POWER_ON: + case PWRDM_POWER_INACTIVE: + save_state = 0; + break; + case PWRDM_POWER_OFF: + save_state = 1; + break; + case PWRDM_POWER_RET: + default: + /* + * CPUx CSWR is invalid hardware state. Also CPUx OSWR + * doesn't make much scense, since logic is lost and $L1 + * needs to be cleaned because of coherency. This makes + * CPUx OSWR equivalent to CPUX OFF and hence not supported + */ + WARN_ON(1); + goto ret; + } + + /* + * MPUSS book keeping should be executed by master + * CPU only which is also the last CPU to go down. + */ + if (cpu) + goto cpu_prepare; + + pwrdm_pre_transition(); + + /* + * Check MPUSS next state and save GIC if needed + * GIC lost during MPU OFF and OSWR + */ + pwrdm_clear_all_prev_pwrst(mpuss_pd); + if (pwrdm_read_next_pwrst(mpuss_pd) == PWRDM_POWER_RET) { + if (pwrdm_read_logic_retst(mpuss_pd) == PWRDM_POWER_OFF) { + if (omap_type() != OMAP2_DEVICE_TYPE_GP) { + save_gic_wakeupgen_secure(); + } else { + omap_wakeupgen_save(); + gic_save_context(); + } + } + } + if (pwrdm_read_next_pwrst(mpuss_pd) == PWRDM_POWER_OFF) { + if (omap_type() != OMAP2_DEVICE_TYPE_GP) { + save_secure_ram(); + save_gic_wakeupgen_secure(); + } else { + omap_wakeupgen_save(); + gic_save_context(); + } + save_state = 3; + } + +cpu_prepare: + clear_cpu_prev_pwrst(cpu); + set_cpu_next_pwrst(cpu, power_state); + scu_pwrst_prepare(cpu, power_state); + + /* + * Call low level function with targeted CPU id + * and its low power state. + */ + omap4_cpu_suspend(cpu, save_state); + + /* + * Restore the CPUx power state to ON otherwise CPUx + * power domain can transitions to programmed low power + * state while doing WFI outside the low powe code. On + * secure devices, CPUx does WFI which can result in + * domain transition + */ + wakeup_cpu = hard_smp_processor_id(); + set_cpu_next_pwrst(wakeup_cpu, PWRDM_POWER_ON); + + /* If !master cpu return to hotplug-path */ + if (wakeup_cpu) + goto ret; + + /* Check MPUSS previous power state and enable GIC if needed */ + if (pwrdm_read_prev_pwrst(mpuss_pd) == PWRDM_POWER_OFF) { + /* Clear SAR BACKUP status */ + __raw_writel(0x0, sar_base + SAR_BACKUP_STATUS_OFFSET); + /* Enable GIC distributor and inteface on CPU0*/ + gic_cpu_enable(); + gic_dist_enable(); + } + + pwrdm_post_transition(); + +ret: + return 0; +} + +static void save_l2x0_auxctrl(void) +{ +#ifdef CONFIG_CACHE_L2X0 + /* + * Save the L2X0 AUXCTRL value to SAR memory. Its used to + * in every restore patch MPUSS OFF path. + */ + void __iomem *l2x0_base = omap4_get_l2cache_base(); + u32 val; + + val = __raw_readl(l2x0_base + L2X0_AUX_CTRL); + __raw_writel(val, sar_base + L2X0_AUXCTRL_OFFSET); +#endif +} + +/* + * Initialise OMAP4 MPUSS + */ +int __init omap4_mpuss_init(void) +{ + struct omap4_cpu_pm_info *pm_info; + u8 i; + + /* Get GIC and SAR RAM base addresses */ + sar_base = omap4_get_sar_ram_base(); + gic_dist_base = omap4_get_gic_dist_base(); + + if (omap_rev() == OMAP4430_REV_ES1_0) { + WARN(1, "Power Management not supported on OMAP4430 ES1.0\n"); + return -ENODEV; + } + + /* Initilaise per CPU PM information */ + pm_info = &per_cpu(omap4_pm_info, 0x0); + pm_info->scu_sar_addr = sar_base + SCU_OFFSET0; + pm_info->pwrdm = pwrdm_lookup("cpu0_pwrdm"); + if (!pm_info->pwrdm) { + pr_err("Lookup failed for CPU0 pwrdm\n"); + return -ENODEV; + } + + /* Clear CPU previous power domain state */ + pwrdm_clear_all_prev_pwrst(pm_info->pwrdm); + + /* Initialise CPU0 power domain state to ON */ + pwrdm_set_next_pwrst(pm_info->pwrdm, PWRDM_POWER_ON); + + pm_info = &per_cpu(omap4_pm_info, 0x1); + pm_info->scu_sar_addr = sar_base + SCU_OFFSET1; + pm_info->pwrdm = pwrdm_lookup("cpu1_pwrdm"); + if (!pm_info->pwrdm) { + pr_err("Lookup failed for CPU1 pwrdm\n"); + return -ENODEV; + } + + /* + * Check the OMAP type and store it to scratchpad + */ + if (omap_type() != OMAP2_DEVICE_TYPE_GP) { + /* Memory not released */ + secure_ram = dma_alloc_coherent(NULL, OMAP4_SECURE_RAM_STORAGE, + (dma_addr_t *)&omap4_secure_ram_phys, GFP_ATOMIC); + if (!secure_ram) + pr_err("Unable to allocate secure ram storage\n"); + writel(0x1, sar_base + OMAP_TYPE_OFFSET); + } else { + writel(0x0, sar_base + OMAP_TYPE_OFFSET); + } + + /* Clear CPU previous power domain state */ + pwrdm_clear_all_prev_pwrst(pm_info->pwrdm); + + /* Initialise CPU1 power domain state to ON */ + pwrdm_set_next_pwrst(pm_info->pwrdm, PWRDM_POWER_ON); + + /* + * Program the wakeup routine address for the CPU0 and CPU1 + * used for OFF or DORMANT wakeup. Wakeup routine address + * is fixed so programit in init itself. + */ + __raw_writel(virt_to_phys(omap4_cpu_resume), + sar_base + CPU1_WAKEUP_NS_PA_ADDR_OFFSET); + __raw_writel(virt_to_phys(omap4_cpu_resume), + sar_base + CPU0_WAKEUP_NS_PA_ADDR_OFFSET); + + mpuss_pd = pwrdm_lookup("mpu_pwrdm"); + if (!mpuss_pd) { + pr_err("Failed to get lookup for MPUSS pwrdm\n"); + return -ENODEV; + } + + /* Clear CPU previous power domain state */ + pwrdm_clear_all_prev_pwrst(mpuss_pd); + + /* + * Find out how many interrupts are supported. + * OMAP4 supports max of 128 SPIs where as GIC can support + * up to 1020 interrupt sources. On OMAP4, maximum SPIs are + * fused in DIST_CTR bit-fields as 128. Hence the code is safe + * from reserved register writes since its well within 1020. + */ + max_spi_reg = __raw_readl(gic_dist_base + GIC_DIST_CTR) & 0x1f; + max_spi_irq = max_spi_reg * 32; + + /* + * Mark the PPI and SPI interrupts as non-secure. + * program the SAR locations for interrupt security registers to + * reflect the same. + */ + if (omap_type() == OMAP2_DEVICE_TYPE_GP) { + sar_writel(GIC_ISR_NON_SECURE, ICDISR_CPU0_OFFSET, 0); + sar_writel(GIC_ISR_NON_SECURE, ICDISR_CPU1_OFFSET, 0); + for (i = 0; i < max_spi_reg; i++) + sar_writel(GIC_ISR_NON_SECURE, ICDISR_SPI_OFFSET, i); + } + save_l2x0_auxctrl(); + + return 0; +} + +#endif + diff --git a/arch/arm/mach-omap2/omap4-sar-layout.h b/arch/arm/mach-omap2/omap4-sar-layout.h new file mode 100644 index 000000000000..58b82c20e910 --- /dev/null +++ b/arch/arm/mach-omap2/omap4-sar-layout.h @@ -0,0 +1,67 @@ +/* + * omap4-sar-layout.h: OMAP4 SAR RAM layout header file + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Santosh Shilimkar <santosh.shilimkar@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. + */ +#ifndef OMAP_ARCH_OMAP4_SAR_LAYOUT_H +#define OMAP_ARCH_OMAP4_SAR_LAYOUT_H + +/* + * SAR BANK offsets from base address OMAP44XX_SAR_RAM_BASE + */ +#define SAR_BANK1_OFFSET 0x0000 +#define SAR_BANK2_OFFSET 0x1000 +#define SAR_BANK3_OFFSET 0x2000 +#define SAR_BANK4_OFFSET 0x3000 + +/* Scratch pad memory offsets from SAR_BANK1 */ +#define CPU0_SAVE_OFFSET 0xb00 +#define CPU1_SAVE_OFFSET 0xc00 +#define MMU_OFFSET 0xd00 +#define SCU_OFFSET0 0xd20 +#define SCU_OFFSET1 0xd24 +#define L2X0_OFFSET 0xd28 +#define L2X0_AUXCTRL_OFFSET 0xd2c +#define OMAP_TYPE_OFFSET 0xd48 + +/* CPUx Wakeup Non-Secure Physical Address offsets in SAR_BANK3 */ +#define CPU0_WAKEUP_NS_PA_ADDR_OFFSET 0xa04 +#define CPU1_WAKEUP_NS_PA_ADDR_OFFSET 0xa08 + +/* GIC save restore offset from SAR_BANK3 */ +#define SAR_BACKUP_STATUS_OFFSET (SAR_BANK3_OFFSET + 0x500) +#define SAR_SECURE_RAM_SIZE_OFFSET (SAR_BANK3_OFFSET + 0x504) +#define SAR_SECRAM_SAVED_AT_OFFSET (SAR_BANK3_OFFSET + 0x508) +#define ICDISR_CPU0_OFFSET (SAR_BANK3_OFFSET + 0x50c) +#define ICDISR_CPU1_OFFSET (SAR_BANK3_OFFSET + 0x510) +#define ICDISR_SPI_OFFSET (SAR_BANK3_OFFSET + 0x514) +#define ICDISER_CPU0_OFFSET (SAR_BANK3_OFFSET + 0x524) +#define ICDISER_CPU1_OFFSET (SAR_BANK3_OFFSET + 0x528) +#define ICDISER_SPI_OFFSET (SAR_BANK3_OFFSET + 0x52c) +#define ICDIPR_SFI_CPU0_OFFSET (SAR_BANK3_OFFSET + 0x53c) +#define ICDIPR_PPI_CPU0_OFFSET (SAR_BANK3_OFFSET + 0x54c) +#define ICDIPR_SFI_CPU1_OFFSET (SAR_BANK3_OFFSET + 0x550) +#define ICDIPR_PPI_CPU1_OFFSET (SAR_BANK3_OFFSET + 0x560) +#define ICDIPR_SPI_OFFSET (SAR_BANK3_OFFSET + 0x564) +#define ICDIPTR_SPI_OFFSET (SAR_BANK3_OFFSET + 0x5e4) +#define ICDICFR_OFFSET (SAR_BANK3_OFFSET + 0x664) +#define SAR_BACKUP_STATUS_GIC_CPU0 0x1 +#define SAR_BACKUP_STATUS_GIC_CPU1 0x2 + +/* WakeUpGen save restore offset from OMAP44XX_SAR_RAM_BASE */ +#define WAKEUPGENENB_OFFSET_CPU0 (SAR_BANK3_OFFSET + 0x684) +#define WAKEUPGENENB_SECURE_OFFSET_CPU0 (SAR_BANK3_OFFSET + 0x694) +#define WAKEUPGENENB_OFFSET_CPU1 (SAR_BANK3_OFFSET + 0x6a4) +#define WAKEUPGENENB_SECURE_OFFSET_CPU1 (SAR_BANK3_OFFSET + 0x6b4) +#define AUXCOREBOOT0_OFFSET (SAR_BANK3_OFFSET + 0x6c4) +#define AUXCOREBOOT1_OFFSET (SAR_BANK3_OFFSET + 0x6c8) +#define PTMSYNCREQ_MASK_OFFSET (SAR_BANK3_OFFSET + 0x6cc) +#define PTMSYNCREQ_EN_OFFSET (SAR_BANK3_OFFSET + 0x6d0) +#define SAR_BACKUP_STATUS_WAKEUPGEN 0x10 + +#endif diff --git a/arch/arm/mach-omap2/omap44xx-smc.S b/arch/arm/mach-omap2/omap44xx-smc.S index e69d37d95204..83ba6d97b45b 100644 --- a/arch/arm/mach-omap2/omap44xx-smc.S +++ b/arch/arm/mach-omap2/omap44xx-smc.S @@ -31,6 +31,30 @@ ENTRY(omap_smc1) ldmfd sp!, {r2-r12, pc} ENDPROC(omap_smc1) +/* + * Low level common routine to manage secure + * HAL APIs. + * Function signature : u32 omap_smc2(u32 id, u32 falg, u32 pargs) + * @id : Application ID of HAL APIs + * @flag : Flag to indicate the criticality of operation + * @pargs : Physical address of parameter list starting + * with number of parametrs + */ +ENTRY(omap_smc2) + stmfd sp!, {r1-r12, lr} + mov r3, r2 + mov r2, r1 + mov r1, #0x0 @ Process ID + mov r6, #0xff + mov r12, #0x00 @ Secure Service ID + mov r7, #0 + mcr p15, 0, r7, c7, c5, 6 + dsb + dmb + smc #0 + ldmfd sp!, {r1-r12, pc} +END(omap_smc2) + ENTRY(omap_modify_auxcoreboot0) stmfd sp!, {r1-r12, lr} ldr r12, =0x104 diff --git a/arch/arm/mach-omap2/omap4_trim_quirks.c b/arch/arm/mach-omap2/omap4_trim_quirks.c new file mode 100644 index 000000000000..0ee5bb0bc78b --- /dev/null +++ b/arch/arm/mach-omap2/omap4_trim_quirks.c @@ -0,0 +1,78 @@ +/* + * OMAP LDO control and configuration + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Nishanth Menon + * + * 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. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/cpu.h> + +#include "control.h" + +/** + * omap4_ldo_trim_configure() - Handle device trim variance + * + * Few of the silicon out of the fab come out without trim parameters + * efused in. These need some software support to allow the device to + * function normally. Handle these silicon quirks here. + */ +static int __init omap4_ldo_trim_configure(void) +{ + u32 is_trimmed = 0; + u32 val; + + /* Applicable only for OMAP4 */ + if (!cpu_is_omap44xx()) + return 0; + + /* + * Some ES2.2 efuse values for BGAP and SLDO trim + * are not programmed. For these units + * 1. we can set overide mode for SLDO trim, + * and program the max multiplication factor, to ensure + * high enough voltage on SLDO output. + * 2. trim VDAC value for TV output as per recomendation + */ + + if (omap_rev() == CHIP_IS_OMAP4430ES2_2) + is_trimmed = omap_ctrl_readl( + OMAP4_CTRL_MODULE_CORE_LDOSRAM_MPU_VOLTAGE_CTRL); + + /* if not trimmed, we set force overide, insted of efuse. */ + if (!is_trimmed) { + /* Fill in recommended values */ + val = 0x0f << OMAP4_LDOSRAMCORE_ACTMODE_VSET_OUT_SHIFT; + val |= OMAP4_LDOSRAMCORE_ACTMODE_MUX_CTRL_MASK; + val |= 0x1 << OMAP4_LDOSRAMCORE_RETMODE_VSET_OUT_SHIFT; + val |= OMAP4_LDOSRAMCORE_RETMODE_MUX_CTRL_MASK; + + omap_ctrl_writel(val, + OMAP4_CTRL_MODULE_CORE_LDOSRAM_MPU_VOLTAGE_CTRL); + omap_ctrl_writel(val, + OMAP4_CTRL_MODULE_CORE_LDOSRAM_CORE_VOLTAGE_CTRL); + omap_ctrl_writel(val, + OMAP4_CTRL_MODULE_CORE_LDOSRAM_IVA_VOLTAGE_CTRL); + + /* write value as per trim recomendation */ + val = 0xc0 << OMAP4_AVDAC_TRIM_BYTE0_SHIFT; + val |= 0x01 << OMAP4_AVDAC_TRIM_BYTE1_SHIFT; + omap_ctrl_writel(val, + OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_EFUSE_1); + } + + /* + * For all ESx.y trimmed and untrimmed units LPDDR IO and + * Smart IO override efuse. + */ + val = OMAP4_LPDDR2_PTV_P5_MASK | OMAP4_LPDDR2_PTV_N5_MASK; + omap_ctrl_writel(val, OMAP4_CTRL_MODULE_PAD_CORE_CONTROL_EFUSE_2); + + return 0; +} +arch_initcall(omap4_ldo_trim_configure); diff --git a/arch/arm/mach-omap2/omap_hwmod.c b/arch/arm/mach-omap2/omap_hwmod.c index c37b9a82388e..3fc816fa0435 100644 --- a/arch/arm/mach-omap2/omap_hwmod.c +++ b/arch/arm/mach-omap2/omap_hwmod.c @@ -1223,6 +1223,7 @@ static int _reset(struct omap_hwmod *oh) static int _enable(struct omap_hwmod *oh) { int r; + int hwsup = 0; if (oh->_state != _HWMOD_STATE_INITIALIZED && oh->_state != _HWMOD_STATE_IDLE && @@ -1251,10 +1252,16 @@ static int _enable(struct omap_hwmod *oh) omap_hwmod_mux(oh->mux, _HWMOD_STATE_ENABLED); _add_initiator_dep(oh, mpu_oh); + if (oh->_clk && oh->_clk->clkdm) { + hwsup = clkdm_is_idle(oh->_clk->clkdm); + clkdm_wakeup(oh->_clk->clkdm); + } _enable_clocks(oh); - r = _wait_target_ready(oh); if (!r) { + if (oh->_clk && oh->_clk->clkdm && hwsup) + clkdm_allow_idle(oh->_clk->clkdm); + oh->_state = _HWMOD_STATE_ENABLED; /* Access the sysconfig only if the target is ready */ diff --git a/arch/arm/mach-omap2/omap_hwmod_3xxx_data.c b/arch/arm/mach-omap2/omap_hwmod_3xxx_data.c index 909a84de6682..eb639d1ecaf3 100644 --- a/arch/arm/mach-omap2/omap_hwmod_3xxx_data.c +++ b/arch/arm/mach-omap2/omap_hwmod_3xxx_data.c @@ -29,6 +29,7 @@ #include "omap_hwmod_common_data.h" +#include "smartreflex.h" #include "prm-regbits-34xx.h" #include "cm-regbits-34xx.h" #include "wd_timer.h" @@ -2910,6 +2911,10 @@ static struct omap_hwmod_class omap36xx_smartreflex_hwmod_class = { }; /* SR1 */ +static struct omap_smartreflex_dev_attr sr1_dev_attr = { + .sensor_voltdm_name = "mpu_iva", +}; + static struct omap_hwmod_ocp_if *omap3_sr1_slaves[] = { &omap3_l4_core__sr1, }; @@ -2918,7 +2923,6 @@ static struct omap_hwmod omap34xx_sr1_hwmod = { .name = "sr1_hwmod", .class = &omap34xx_smartreflex_hwmod_class, .main_clk = "sr1_fck", - .vdd_name = "mpu", .prcm = { .omap2 = { .prcm_reg_id = 1, @@ -2930,6 +2934,7 @@ static struct omap_hwmod omap34xx_sr1_hwmod = { }, .slaves = omap3_sr1_slaves, .slaves_cnt = ARRAY_SIZE(omap3_sr1_slaves), + .dev_attr = &sr1_dev_attr, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES2 | CHIP_IS_OMAP3430ES3_0 | CHIP_IS_OMAP3430ES3_1), @@ -2940,7 +2945,6 @@ static struct omap_hwmod omap36xx_sr1_hwmod = { .name = "sr1_hwmod", .class = &omap36xx_smartreflex_hwmod_class, .main_clk = "sr1_fck", - .vdd_name = "mpu", .prcm = { .omap2 = { .prcm_reg_id = 1, @@ -2952,10 +2956,15 @@ static struct omap_hwmod omap36xx_sr1_hwmod = { }, .slaves = omap3_sr1_slaves, .slaves_cnt = ARRAY_SIZE(omap3_sr1_slaves), + .dev_attr = &sr1_dev_attr, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3630ES1), }; /* SR2 */ +static struct omap_smartreflex_dev_attr sr2_dev_attr = { + .sensor_voltdm_name = "core", +}; + static struct omap_hwmod_ocp_if *omap3_sr2_slaves[] = { &omap3_l4_core__sr2, }; @@ -2964,7 +2973,6 @@ static struct omap_hwmod omap34xx_sr2_hwmod = { .name = "sr2_hwmod", .class = &omap34xx_smartreflex_hwmod_class, .main_clk = "sr2_fck", - .vdd_name = "core", .prcm = { .omap2 = { .prcm_reg_id = 1, @@ -2976,6 +2984,7 @@ static struct omap_hwmod omap34xx_sr2_hwmod = { }, .slaves = omap3_sr2_slaves, .slaves_cnt = ARRAY_SIZE(omap3_sr2_slaves), + .dev_attr = &sr2_dev_attr, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES2 | CHIP_IS_OMAP3430ES3_0 | CHIP_IS_OMAP3430ES3_1), @@ -2986,7 +2995,6 @@ static struct omap_hwmod omap36xx_sr2_hwmod = { .name = "sr2_hwmod", .class = &omap36xx_smartreflex_hwmod_class, .main_clk = "sr2_fck", - .vdd_name = "core", .prcm = { .omap2 = { .prcm_reg_id = 1, @@ -2998,6 +3006,7 @@ static struct omap_hwmod omap36xx_sr2_hwmod = { }, .slaves = omap3_sr2_slaves, .slaves_cnt = ARRAY_SIZE(omap3_sr2_slaves), + .dev_attr = &sr2_dev_attr, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3630ES1), }; diff --git a/arch/arm/mach-omap2/omap_hwmod_44xx_data.c b/arch/arm/mach-omap2/omap_hwmod_44xx_data.c index 841b7345e2fb..81d40a5b094a 100644 --- a/arch/arm/mach-omap2/omap_hwmod_44xx_data.c +++ b/arch/arm/mach-omap2/omap_hwmod_44xx_data.c @@ -30,6 +30,7 @@ #include "omap_hwmod_common_data.h" +#include "smartreflex.h" #include "cm1_44xx.h" #include "cm2_44xx.h" #include "prm44xx.h" @@ -661,6 +662,222 @@ static struct omap_hwmod omap44xx_mpu_private_hwmod = { */ /* + * 'mpu' class + * mpu sub-system + */ + +static struct omap_hwmod_class omap44xx_mpu_hwmod_class = { + .name = "mpu", +}; + +/* mpu */ +static struct omap_hwmod_irq_info omap44xx_mpu_irqs[] = { + { .name = "pl310", .irq = 0 + OMAP44XX_IRQ_GIC_START }, + { .name = "cti0", .irq = 1 + OMAP44XX_IRQ_GIC_START }, + { .name = "cti1", .irq = 2 + OMAP44XX_IRQ_GIC_START }, +}; + +/* mpu master ports */ +static struct omap_hwmod_ocp_if *omap44xx_mpu_masters[] = { + &omap44xx_mpu__l3_main_1, + &omap44xx_mpu__l4_abe, + &omap44xx_mpu__dmm, +}; + +static struct omap_hwmod omap44xx_mpu_hwmod = { + .name = "mpu", + .class = &omap44xx_mpu_hwmod_class, + .flags = (HWMOD_INIT_NO_IDLE | HWMOD_INIT_NO_RESET), + .mpu_irqs = omap44xx_mpu_irqs, + .mpu_irqs_cnt = ARRAY_SIZE(omap44xx_mpu_irqs), + .main_clk = "dpll_mpu_m2_ck", + .prcm = { + .omap4 = { + .clkctrl_reg = OMAP4430_CM_MPU_MPU_CLKCTRL, + }, + }, + .masters = omap44xx_mpu_masters, + .masters_cnt = ARRAY_SIZE(omap44xx_mpu_masters), + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), +}; + +/* + * 'smartreflex' class + * smartreflex module (monitor silicon performance and outputs a measure of + * performance error) + */ + +/* The IP is not compliant to type1 / type2 scheme */ +static struct omap_hwmod_sysc_fields omap_hwmod_sysc_type_smartreflex = { + .sidle_shift = 24, + .enwkup_shift = 26, +}; + +static struct omap_hwmod_class_sysconfig omap44xx_smartreflex_sysc = { + .sysc_offs = 0x0038, + .sysc_flags = (SYSC_HAS_ENAWAKEUP | SYSC_HAS_SIDLEMODE), + .idlemodes = (SIDLE_FORCE | SIDLE_NO | SIDLE_SMART | + SIDLE_SMART_WKUP), + .sysc_fields = &omap_hwmod_sysc_type_smartreflex, +}; + +static struct omap_hwmod_class omap44xx_smartreflex_hwmod_class = { + .name = "smartreflex", + .sysc = &omap44xx_smartreflex_sysc, + .rev = 2, +}; + +/* smartreflex_core */ +static struct omap_smartreflex_dev_attr smartreflex_core_dev_attr = { + .sensor_voltdm_name = "core", +}; + +static struct omap_hwmod omap44xx_smartreflex_core_hwmod; +static struct omap_hwmod_irq_info omap44xx_smartreflex_core_irqs[] = { + { .irq = 19 + OMAP44XX_IRQ_GIC_START }, +}; + +static struct omap_hwmod_addr_space omap44xx_smartreflex_core_addrs[] = { + { + .pa_start = 0x4a0dd000, + .pa_end = 0x4a0dd03f, + .flags = ADDR_TYPE_RT + }, +}; + +/* l4_cfg -> smartreflex_core */ +static struct omap_hwmod_ocp_if omap44xx_l4_cfg__smartreflex_core = { + .master = &omap44xx_l4_cfg_hwmod, + .slave = &omap44xx_smartreflex_core_hwmod, + .clk = "l4_div_ck", + .addr = omap44xx_smartreflex_core_addrs, + .addr_cnt = ARRAY_SIZE(omap44xx_smartreflex_core_addrs), + .user = OCP_USER_MPU | OCP_USER_SDMA, +}; + +/* smartreflex_core slave ports */ +static struct omap_hwmod_ocp_if *omap44xx_smartreflex_core_slaves[] = { + &omap44xx_l4_cfg__smartreflex_core, +}; + +static struct omap_hwmod omap44xx_smartreflex_core_hwmod = { + .name = "smartreflex_core", + .class = &omap44xx_smartreflex_hwmod_class, + .mpu_irqs = omap44xx_smartreflex_core_irqs, + .mpu_irqs_cnt = ARRAY_SIZE(omap44xx_smartreflex_core_irqs), + .main_clk = "smartreflex_core_fck", + .prcm = { + .omap4 = { + .clkctrl_reg = OMAP4430_CM_ALWON_SR_CORE_CLKCTRL, + }, + }, + .slaves = omap44xx_smartreflex_core_slaves, + .slaves_cnt = ARRAY_SIZE(omap44xx_smartreflex_core_slaves), + .dev_attr = &smartreflex_core_dev_attr, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), +}; + +/* smartreflex_iva */ +static struct omap_smartreflex_dev_attr smartreflex_iva_dev_attr = { + .sensor_voltdm_name = "iva", +}; + +static struct omap_hwmod omap44xx_smartreflex_iva_hwmod; +static struct omap_hwmod_irq_info omap44xx_smartreflex_iva_irqs[] = { + { .irq = 102 + OMAP44XX_IRQ_GIC_START }, +}; + +static struct omap_hwmod_addr_space omap44xx_smartreflex_iva_addrs[] = { + { + .pa_start = 0x4a0db000, + .pa_end = 0x4a0db03f, + .flags = ADDR_TYPE_RT + }, +}; + +/* l4_cfg -> smartreflex_iva */ +static struct omap_hwmod_ocp_if omap44xx_l4_cfg__smartreflex_iva = { + .master = &omap44xx_l4_cfg_hwmod, + .slave = &omap44xx_smartreflex_iva_hwmod, + .clk = "l4_div_ck", + .addr = omap44xx_smartreflex_iva_addrs, + .addr_cnt = ARRAY_SIZE(omap44xx_smartreflex_iva_addrs), + .user = OCP_USER_MPU | OCP_USER_SDMA, +}; + +/* smartreflex_iva slave ports */ +static struct omap_hwmod_ocp_if *omap44xx_smartreflex_iva_slaves[] = { + &omap44xx_l4_cfg__smartreflex_iva, +}; + +static struct omap_hwmod omap44xx_smartreflex_iva_hwmod = { + .name = "smartreflex_iva", + .class = &omap44xx_smartreflex_hwmod_class, + .mpu_irqs = omap44xx_smartreflex_iva_irqs, + .mpu_irqs_cnt = ARRAY_SIZE(omap44xx_smartreflex_iva_irqs), + .main_clk = "smartreflex_iva_fck", + .prcm = { + .omap4 = { + .clkctrl_reg = OMAP4430_CM_ALWON_SR_IVA_CLKCTRL, + }, + }, + .slaves = omap44xx_smartreflex_iva_slaves, + .slaves_cnt = ARRAY_SIZE(omap44xx_smartreflex_iva_slaves), + .dev_attr = &smartreflex_iva_dev_attr, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), +}; + +/* smartreflex_mpu */ +static struct omap_smartreflex_dev_attr smartreflex_mpu_dev_attr = { + .sensor_voltdm_name = "mpu", +}; + +static struct omap_hwmod omap44xx_smartreflex_mpu_hwmod; +static struct omap_hwmod_irq_info omap44xx_smartreflex_mpu_irqs[] = { + { .irq = 18 + OMAP44XX_IRQ_GIC_START }, +}; + +static struct omap_hwmod_addr_space omap44xx_smartreflex_mpu_addrs[] = { + { + .pa_start = 0x4a0d9000, + .pa_end = 0x4a0d903f, + .flags = ADDR_TYPE_RT + }, +}; + +/* l4_cfg -> smartreflex_mpu */ +static struct omap_hwmod_ocp_if omap44xx_l4_cfg__smartreflex_mpu = { + .master = &omap44xx_l4_cfg_hwmod, + .slave = &omap44xx_smartreflex_mpu_hwmod, + .clk = "l4_div_ck", + .addr = omap44xx_smartreflex_mpu_addrs, + .addr_cnt = ARRAY_SIZE(omap44xx_smartreflex_mpu_addrs), + .user = OCP_USER_MPU | OCP_USER_SDMA, +}; + +/* smartreflex_mpu slave ports */ +static struct omap_hwmod_ocp_if *omap44xx_smartreflex_mpu_slaves[] = { + &omap44xx_l4_cfg__smartreflex_mpu, +}; + +static struct omap_hwmod omap44xx_smartreflex_mpu_hwmod = { + .name = "smartreflex_mpu", + .class = &omap44xx_smartreflex_hwmod_class, + .mpu_irqs = omap44xx_smartreflex_mpu_irqs, + .mpu_irqs_cnt = ARRAY_SIZE(omap44xx_smartreflex_mpu_irqs), + .main_clk = "smartreflex_mpu_fck", + .prcm = { + .omap4 = { + .clkctrl_reg = OMAP4430_CM_ALWON_SR_MPU_CLKCTRL, + }, + }, + .slaves = omap44xx_smartreflex_mpu_slaves, + .slaves_cnt = ARRAY_SIZE(omap44xx_smartreflex_mpu_slaves), + .dev_attr = &smartreflex_mpu_dev_attr, + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), +}; + +/* * 'aess' class * audio engine sub system */ @@ -1108,6 +1325,7 @@ static struct omap_hwmod omap44xx_dsp_c0_hwmod = { static struct omap_hwmod omap44xx_dsp_hwmod = { .name = "dsp", .class = &omap44xx_dsp_hwmod_class, + .flags = HWMOD_INIT_NO_RESET, .mpu_irqs = omap44xx_dsp_irqs, .mpu_irqs_cnt = ARRAY_SIZE(omap44xx_dsp_irqs), .rst_lines = omap44xx_dsp_resets, @@ -1848,8 +2066,8 @@ static struct omap_hwmod_opt_clk gpio2_opt_clks[] = { static struct omap_hwmod omap44xx_gpio2_hwmod = { .name = "gpio2", .class = &omap44xx_gpio_hwmod_class, - .flags = HWMOD_CONTROL_OPT_CLKS_IN_RESET | HWMOD_INIT_NO_IDLE | - HWMOD_INIT_NO_RESET, + .flags = HWMOD_CONTROL_OPT_CLKS_IN_RESET | + HWMOD_SWSUP_SIDLE , .mpu_irqs = omap44xx_gpio2_irqs, .mpu_irqs_cnt = ARRAY_SIZE(omap44xx_gpio2_irqs), .main_clk = "gpio2_ick", @@ -2217,7 +2435,6 @@ static struct omap_hwmod omap44xx_gpu_hwmod = { .mpu_irqs = omap44xx_gpu_irqs, .mpu_irqs_cnt = ARRAY_SIZE(omap44xx_gpu_irqs), .main_clk = "gpu_fck", - .vdd_name = "core", .prcm = { .omap4 = { .clkctrl_reg = OMAP4430_CM_GFX_GFX_CLKCTRL, @@ -2227,6 +2444,7 @@ static struct omap_hwmod omap44xx_gpu_hwmod = { .slaves_cnt = ARRAY_SIZE(omap44xx_gpu_slaves), .masters = omap44xx_gpu_masters, .masters_cnt = ARRAY_SIZE(omap44xx_gpu_masters), + .dev_attr = &smartreflex_core_dev_attr, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), }; @@ -3833,210 +4051,6 @@ static struct omap_hwmod omap44xx_mmc5_hwmod = { }; /* - * 'mpu' class - * mpu sub-system - */ - -static struct omap_hwmod_class omap44xx_mpu_hwmod_class = { - .name = "mpu", -}; - -/* mpu */ -static struct omap_hwmod_irq_info omap44xx_mpu_irqs[] = { - { .name = "pl310", .irq = 0 + OMAP44XX_IRQ_GIC_START }, - { .name = "cti0", .irq = 1 + OMAP44XX_IRQ_GIC_START }, - { .name = "cti1", .irq = 2 + OMAP44XX_IRQ_GIC_START }, -}; - -/* mpu master ports */ -static struct omap_hwmod_ocp_if *omap44xx_mpu_masters[] = { - &omap44xx_mpu__l3_main_1, - &omap44xx_mpu__l4_abe, - &omap44xx_mpu__dmm, -}; - -static struct omap_hwmod omap44xx_mpu_hwmod = { - .name = "mpu", - .class = &omap44xx_mpu_hwmod_class, - .flags = (HWMOD_INIT_NO_IDLE | HWMOD_INIT_NO_RESET), - .mpu_irqs = omap44xx_mpu_irqs, - .mpu_irqs_cnt = ARRAY_SIZE(omap44xx_mpu_irqs), - .main_clk = "dpll_mpu_m2_ck", - .prcm = { - .omap4 = { - .clkctrl_reg = OMAP4430_CM_MPU_MPU_CLKCTRL, - }, - }, - .masters = omap44xx_mpu_masters, - .masters_cnt = ARRAY_SIZE(omap44xx_mpu_masters), - .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), -}; - -/* - * 'smartreflex' class - * smartreflex module (monitor silicon performance and outputs a measure of - * performance error) - */ - -/* The IP is not compliant to type1 / type2 scheme */ -static struct omap_hwmod_sysc_fields omap_hwmod_sysc_type_smartreflex = { - .sidle_shift = 24, - .enwkup_shift = 26, -}; - -static struct omap_hwmod_class_sysconfig omap44xx_smartreflex_sysc = { - .sysc_offs = 0x0038, - .sysc_flags = (SYSC_HAS_ENAWAKEUP | SYSC_HAS_SIDLEMODE), - .idlemodes = (SIDLE_FORCE | SIDLE_NO | SIDLE_SMART | - SIDLE_SMART_WKUP), - .sysc_fields = &omap_hwmod_sysc_type_smartreflex, -}; - -static struct omap_hwmod_class omap44xx_smartreflex_hwmod_class = { - .name = "smartreflex", - .sysc = &omap44xx_smartreflex_sysc, - .rev = 2, -}; - -/* smartreflex_core */ -static struct omap_hwmod omap44xx_smartreflex_core_hwmod; -static struct omap_hwmod_irq_info omap44xx_smartreflex_core_irqs[] = { - { .irq = 19 + OMAP44XX_IRQ_GIC_START }, -}; - -static struct omap_hwmod_addr_space omap44xx_smartreflex_core_addrs[] = { - { - .pa_start = 0x4a0dd000, - .pa_end = 0x4a0dd03f, - .flags = ADDR_TYPE_RT - }, -}; - -/* l4_cfg -> smartreflex_core */ -static struct omap_hwmod_ocp_if omap44xx_l4_cfg__smartreflex_core = { - .master = &omap44xx_l4_cfg_hwmod, - .slave = &omap44xx_smartreflex_core_hwmod, - .clk = "l4_div_ck", - .addr = omap44xx_smartreflex_core_addrs, - .addr_cnt = ARRAY_SIZE(omap44xx_smartreflex_core_addrs), - .user = OCP_USER_MPU | OCP_USER_SDMA, -}; - -/* smartreflex_core slave ports */ -static struct omap_hwmod_ocp_if *omap44xx_smartreflex_core_slaves[] = { - &omap44xx_l4_cfg__smartreflex_core, -}; - -static struct omap_hwmod omap44xx_smartreflex_core_hwmod = { - .name = "smartreflex_core", - .class = &omap44xx_smartreflex_hwmod_class, - .mpu_irqs = omap44xx_smartreflex_core_irqs, - .mpu_irqs_cnt = ARRAY_SIZE(omap44xx_smartreflex_core_irqs), - .main_clk = "smartreflex_core_fck", - .vdd_name = "core", - .prcm = { - .omap4 = { - .clkctrl_reg = OMAP4430_CM_ALWON_SR_CORE_CLKCTRL, - }, - }, - .slaves = omap44xx_smartreflex_core_slaves, - .slaves_cnt = ARRAY_SIZE(omap44xx_smartreflex_core_slaves), - .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), -}; - -/* smartreflex_iva */ -static struct omap_hwmod omap44xx_smartreflex_iva_hwmod; -static struct omap_hwmod_irq_info omap44xx_smartreflex_iva_irqs[] = { - { .irq = 102 + OMAP44XX_IRQ_GIC_START }, -}; - -static struct omap_hwmod_addr_space omap44xx_smartreflex_iva_addrs[] = { - { - .pa_start = 0x4a0db000, - .pa_end = 0x4a0db03f, - .flags = ADDR_TYPE_RT - }, -}; - -/* l4_cfg -> smartreflex_iva */ -static struct omap_hwmod_ocp_if omap44xx_l4_cfg__smartreflex_iva = { - .master = &omap44xx_l4_cfg_hwmod, - .slave = &omap44xx_smartreflex_iva_hwmod, - .clk = "l4_div_ck", - .addr = omap44xx_smartreflex_iva_addrs, - .addr_cnt = ARRAY_SIZE(omap44xx_smartreflex_iva_addrs), - .user = OCP_USER_MPU | OCP_USER_SDMA, -}; - -/* smartreflex_iva slave ports */ -static struct omap_hwmod_ocp_if *omap44xx_smartreflex_iva_slaves[] = { - &omap44xx_l4_cfg__smartreflex_iva, -}; - -static struct omap_hwmod omap44xx_smartreflex_iva_hwmod = { - .name = "smartreflex_iva", - .class = &omap44xx_smartreflex_hwmod_class, - .mpu_irqs = omap44xx_smartreflex_iva_irqs, - .mpu_irqs_cnt = ARRAY_SIZE(omap44xx_smartreflex_iva_irqs), - .main_clk = "smartreflex_iva_fck", - .vdd_name = "iva", - .prcm = { - .omap4 = { - .clkctrl_reg = OMAP4430_CM_ALWON_SR_IVA_CLKCTRL, - }, - }, - .slaves = omap44xx_smartreflex_iva_slaves, - .slaves_cnt = ARRAY_SIZE(omap44xx_smartreflex_iva_slaves), - .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), -}; - -/* smartreflex_mpu */ -static struct omap_hwmod omap44xx_smartreflex_mpu_hwmod; -static struct omap_hwmod_irq_info omap44xx_smartreflex_mpu_irqs[] = { - { .irq = 18 + OMAP44XX_IRQ_GIC_START }, -}; - -static struct omap_hwmod_addr_space omap44xx_smartreflex_mpu_addrs[] = { - { - .pa_start = 0x4a0d9000, - .pa_end = 0x4a0d903f, - .flags = ADDR_TYPE_RT - }, -}; - -/* l4_cfg -> smartreflex_mpu */ -static struct omap_hwmod_ocp_if omap44xx_l4_cfg__smartreflex_mpu = { - .master = &omap44xx_l4_cfg_hwmod, - .slave = &omap44xx_smartreflex_mpu_hwmod, - .clk = "l4_div_ck", - .addr = omap44xx_smartreflex_mpu_addrs, - .addr_cnt = ARRAY_SIZE(omap44xx_smartreflex_mpu_addrs), - .user = OCP_USER_MPU | OCP_USER_SDMA, -}; - -/* smartreflex_mpu slave ports */ -static struct omap_hwmod_ocp_if *omap44xx_smartreflex_mpu_slaves[] = { - &omap44xx_l4_cfg__smartreflex_mpu, -}; - -static struct omap_hwmod omap44xx_smartreflex_mpu_hwmod = { - .name = "smartreflex_mpu", - .class = &omap44xx_smartreflex_hwmod_class, - .mpu_irqs = omap44xx_smartreflex_mpu_irqs, - .mpu_irqs_cnt = ARRAY_SIZE(omap44xx_smartreflex_mpu_irqs), - .main_clk = "smartreflex_mpu_fck", - .vdd_name = "mpu", - .prcm = { - .omap4 = { - .clkctrl_reg = OMAP4430_CM_ALWON_SR_MPU_CLKCTRL, - }, - }, - .slaves = omap44xx_smartreflex_mpu_slaves, - .slaves_cnt = ARRAY_SIZE(omap44xx_smartreflex_mpu_slaves), - .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), -}; - -/* * 'spinlock' class * spinlock provides hardware assistance for synchronizing the processes * running on multiple processors diff --git a/arch/arm/mach-omap2/omap_opp_data.h b/arch/arm/mach-omap2/omap_opp_data.h index c784c12f98a1..076bcd40ab74 100644 --- a/arch/arm/mach-omap2/omap_opp_data.h +++ b/arch/arm/mach-omap2/omap_opp_data.h @@ -49,6 +49,8 @@ */ struct omap_opp_def { char *hwmod_name; + char *voltdm_name; + char *clk_name; unsigned long freq; unsigned long u_volt; @@ -59,9 +61,11 @@ struct omap_opp_def { /* * Initialization wrapper used to define an OPP for OMAP variants. */ -#define OPP_INITIALIZER(_hwmod_name, _enabled, _freq, _uv) \ +#define OPP_INITIALIZER(_hwmod_name, _clk_name, _voltdm_name, _enabled, _freq, _uv) \ { \ .hwmod_name = _hwmod_name, \ + .clk_name = _clk_name, \ + .voltdm_name = _voltdm_name, \ .default_available = _enabled, \ .freq = _freq, \ .u_volt = _uv, \ @@ -86,11 +90,19 @@ extern int __init omap_init_opp_table(struct omap_opp_def *opp_def, extern struct omap_volt_data omap34xx_vddmpu_volt_data[]; extern struct omap_volt_data omap34xx_vddcore_volt_data[]; +extern struct omap_vdd_dep_info omap34xx_vddmpu_dep_info[]; extern struct omap_volt_data omap36xx_vddmpu_volt_data[]; extern struct omap_volt_data omap36xx_vddcore_volt_data[]; +extern struct omap_vdd_dep_info omap36xx_vddmpu_dep_info[]; -extern struct omap_volt_data omap44xx_vdd_mpu_volt_data[]; -extern struct omap_volt_data omap44xx_vdd_iva_volt_data[]; -extern struct omap_volt_data omap44xx_vdd_core_volt_data[]; +extern struct omap_volt_data omap443x_vdd_mpu_volt_data[]; +extern struct omap_volt_data omap443x_vdd_iva_volt_data[]; +extern struct omap_volt_data omap443x_vdd_core_volt_data[]; +extern struct omap_volt_data omap446x_vdd_mpu_volt_data[]; +extern struct omap_volt_data omap446x_vdd_iva_volt_data[]; +extern struct omap_volt_data omap446x_vdd_core_volt_data[]; + +extern struct omap_vdd_dep_info omap443x_vddmpu_dep_info[]; +extern struct omap_vdd_dep_info omap443x_vddiva_dep_info[]; #endif /* __ARCH_ARM_MACH_OMAP2_OMAP_OPP_DATA_H */ diff --git a/arch/arm/mach-omap2/omap_phy_internal.c b/arch/arm/mach-omap2/omap_phy_internal.c index 05f6abc96b0d..7061cf5d42fe 100644 --- a/arch/arm/mach-omap2/omap_phy_internal.c +++ b/arch/arm/mach-omap2/omap_phy_internal.c @@ -50,13 +50,18 @@ int omap4430_phy_init(struct device *dev) { ctrl_base = ioremap(OMAP443X_SCM_BASE, SZ_1K); if (!ctrl_base) { - dev_err(dev, "control module ioremap failed\n"); + pr_err("control module ioremap failed\n"); return -ENOMEM; } /* Power down the phy */ __raw_writel(PHY_PD, ctrl_base + CONTROL_DEV_CONF); - phyclk = clk_get(dev, "ocp2scp_usb_phy_ick"); + if (!dev){ + iounmap(ctrl_base); + return 0; + } + + phyclk = clk_get(dev, "ocp2scp_usb_phy_ick"); if (IS_ERR(phyclk)) { dev_err(dev, "cannot clk_get ocp2scp_usb_phy_ick\n"); iounmap(ctrl_base); diff --git a/arch/arm/mach-omap2/omap_pmic.c b/arch/arm/mach-omap2/omap_pmic.c new file mode 100644 index 000000000000..1ecd60ba6179 --- /dev/null +++ b/arch/arm/mach-omap2/omap_pmic.c @@ -0,0 +1,75 @@ +/* + * Registration hooks for PMICs used with OMAP + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Nishanth Menon + * + * 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. + */ + +#include <linux/init.h> +#include <linux/kernel.h> + +#include "voltage.h" + +#include "pm.h" + +/** + * omap_pmic_init() - trigger point for all PMIC initializers + */ +void __init omap_pmic_init(void) +{ + omap_twl_init(); + omap_tps6236x_init(); +} + +/** + * omap_pmic_register_data() - Register the PMIC information to OMAP mapping + * @omap_pmic__maps: array ending with a empty element representing the maps + */ +int __init omap_pmic_register_data(struct omap_pmic_map *omap_pmic_maps) +{ + struct voltagedomain *voltdm; + struct omap_pmic_map *map; + int r; + + if (!omap_pmic_maps) + return 0; + + map = omap_pmic_maps; + + while(map->name) { + if (!omap_chip_is(map->omap_chip)) + goto next; + + voltdm = voltdm_lookup(map->name); + if (IS_ERR_OR_NULL(voltdm)) { + pr_err("%s: unable to find map %s\n",__func__, + map->name); + goto next; + } + if (IS_ERR_OR_NULL(map->pmic_data)) { + pr_warning("%s: domain[%s] has no pmic data\n",__func__, + map->name); + goto next; + } + + r = omap_voltage_register_pmic(voltdm, map->pmic_data); + if (r) { + pr_warning("%s: domain[%s] register returned %d\n", + __func__, map->name, r); + goto next; + } + if (map->special_action) { + r = map->special_action(voltdm); + WARN(r, "%s: domain[%s] action returned %d\n", __func__, + map->name, r); + } +next: + map++; + } + + return 0; +} diff --git a/arch/arm/mach-omap2/omap_tps6236x.c b/arch/arm/mach-omap2/omap_tps6236x.c new file mode 100644 index 000000000000..da82a5207345 --- /dev/null +++ b/arch/arm/mach-omap2/omap_tps6236x.c @@ -0,0 +1,340 @@ +/* + * OMAP and TPS6236x specific initialization + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ + * Vishwanath BS + * Nishanth Menon + * + * 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. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/i2c/twl.h> + +#include "pm.h" +#include "vc.h" +#include "mux.h" + +/* Voltage limits supported */ +#define MIN_VOLTAGE_TPS62360_62_UV 770000 +#define MAX_VOLTAGE_TPS62360_62_UV 1400000 + +#define MIN_VOLTAGE_TPS62361_UV 500000 +#define MAX_VOLTAGE_TPS62361_UV 1770000 + +#define MAX_VOLTAGE_RAMP_TPS6236X_UV 32000 + +/* + * This is the voltage delta between 2 values in voltage register. + * when switching voltage V1 to V2, TPS62361 can ramp up or down + * initially with step sizes of 20mV with a last step of 10mV. + * In the case of TPS6236[0|2], it is a constant 10mV steps + * we choose the 10mV step for linearity when SR is configured. + */ +#define STEP_SIZE_TPS6236X 10000 + +/* I2C access parameters */ +#define I2C_TPS6236X_SLAVE_ADDR 0x60 + +#define DEF_SET_REG(VSEL0, VSEL1) (((VSEL1) << 1| (VSEL0) << 0) & 0x3) +#define REG_TPS6236X_SET_0 0x00 +#define REG_TPS6236X_SET_1 0x01 +#define REG_TPS6236X_SET_2 0x02 +#define REG_TPS6236X_SET_3 0x03 +#define REG_TPS6236X_CTRL 0x04 +#define REG_TPS6236X_TEMP 0x05 +#define REG_TPS6236X_RAMP_CTRL 0x06 +#define REG_TPS6236X_CHIP_ID0 0x08 +#define REG_TPS6236X_CHIP_ID1 0x09 + +#define MODE_TPS6236X_AUTO_PFM_PWM 0x00 +#define MODE_TPS6236X_FORCE_PWM BIT(7) + +/* We use Auto PFM/PWM mode currently seems to have the best trade off */ +#define VOLTAGE_PFM_MODE_VAL MODE_TPS6236X_AUTO_PFM_PWM + +#define REG_TPS6236X_RAMP_CTRL_RMP_MASK (0x7 << 5) +#define REG_TPS6236X_RAMP_CTRL_EN_DISC BIT(2) +#define REG_TPS6236X_RAMP_CTRL_RAMP_PFM BIT(1) + +#define REG_TPS6236X_CTRL_PD_EN BIT(7) +#define REG_TPS6236X_CTRL_PD_VSEL0 BIT(6) +#define REG_TPS6236X_CTRL_PD_VSEL1 BIT(5) + +/* TWL usage */ +#define TWL6030_REG_SYSEN_CFG_GRP 0x06 +#define TWL6030_BIT_APE_GRP BIT(0) + +/* Voltage params of the attached device (all in uV) */ +static unsigned long voltage_min; +static unsigned long voltage_max; + +/* Which register do we use by default? */ +static int __initdata default_reg = -1;; + +/* Do we need to setup internal pullups? */ +static int __initdata pd_vsel0 = -1; +static int __initdata pd_vsel1 = -1; + +static int __init _bd_setup(char *name,int gpio_vsel, int *pull, int *pd_vsel) +{ + int pull_dir; + int r; + + if (gpio_vsel == -1) { + if (*pull != -1) { + *pd_vsel = (*pull == OMAP_PIN_OFF_OUTPUT_HIGH); + *pull = *pd_vsel; + } else { + *pull = 0; + } + return 0; + } + + /* if we have a pull gpio, with bad dir, pull low */ + if (*pull == -1 || (*pull != OMAP_PIN_OFF_OUTPUT_HIGH && + *pull != OMAP_PIN_OFF_OUTPUT_LOW)) + *pull = OMAP_PIN_OFF_OUTPUT_LOW; + + r = omap_mux_init_gpio(gpio_vsel, *pull); + if (r) { + pr_err("%s: unable to mux gpio%d=%d\n", __func__, + gpio_vsel, r); + goto out; + } + + pull_dir = (*pull == OMAP_PIN_OFF_OUTPUT_HIGH); + *pull = pull_dir; + + r = gpio_request(gpio_vsel, name); + if (r) { + pr_err("%s: unable to req gpio%d=%d\n", __func__, + gpio_vsel, r); + goto out; + } + r = gpio_direction_output(gpio_vsel, pull_dir); + if (r) { + pr_err("%s: unable to pull[%d] gpio%d=%d\n", __func__, + gpio_vsel, pull_dir, r); + gpio_free(gpio_vsel); + goto out; + } +out: + return r; +} + +/* Convert the ramp voltage to ramp value. */ +static u8 __init tps6236x_ramp_value(unsigned long uv) +{ + if (!uv) + return 0; + + if (uv > MAX_VOLTAGE_RAMP_TPS6236X_UV) { + pr_err("%s: uv%ld greater than max %d\n", __func__, + uv, MAX_VOLTAGE_RAMP_TPS6236X_UV); + uv = MAX_VOLTAGE_RAMP_TPS6236X_UV; + } + return fls(MAX_VOLTAGE_RAMP_TPS6236X_UV / uv) - 1; +} + +static unsigned long tps6236x_vsel_to_uv(const u8 vsel) +{ + return (voltage_min + + (STEP_SIZE_TPS6236X * (vsel & ~VOLTAGE_PFM_MODE_VAL))); +} + +static u8 tps6236x_uv_to_vsel(unsigned long uv) +{ + if (!uv) + return 0; + + /* Round off requests to limits */ + if (uv > voltage_max) { + pr_err("%s:Request for overvoltage[%ld] than supported[%ld]\n", + __func__, uv, voltage_max); + uv = voltage_max; + } + if (uv < voltage_min) { + pr_err("%s:Request for undervoltage[%ld] than supported[%ld]\n", + __func__, uv, voltage_min); + uv = voltage_min; + } + return DIV_ROUND_UP(uv - voltage_min, STEP_SIZE_TPS6236X) | + VOLTAGE_PFM_MODE_VAL; +} + +static struct omap_voltdm_pmic omap4_mpu_pmic = { + .slew_rate = 8000, + .step_size = STEP_SIZE_TPS6236X, + .on_volt = 1375000, + .onlp_volt = 1375000, + .ret_volt = 830000, + .off_volt = 0, + .volt_setup_time = 0, + .vp_erroroffset = OMAP4_VP_CONFIG_ERROROFFSET, + .vp_vstepmin = OMAP4_VP_VSTEPMIN_VSTEPMIN, + .vp_vstepmax = OMAP4_VP_VSTEPMAX_VSTEPMAX, + .vp_vddmin = OMAP4_VP_MPU_VLIMITTO_VDDMIN, + .vp_vddmax = OMAP4_VP_MPU_VLIMITTO_VDDMAX, + .vp_timeout_us = OMAP4_VP_VLIMITTO_TIMEOUT_US, + .i2c_slave_addr = I2C_TPS6236X_SLAVE_ADDR, + .volt_reg_addr = REG_TPS6236X_SET_0, + .cmd_reg_addr = REG_TPS6236X_SET_0, + .i2c_high_speed = true, + .i2c_scll_low = 0x28, + .i2c_scll_high = 0x2C, + .i2c_hscll_low = 0x0B, + .i2c_hscll_high = 0x00, + .vsel_to_uv = tps6236x_vsel_to_uv, + .uv_to_vsel = tps6236x_uv_to_vsel, +}; + +/** + * omap4_twl_tps62361_enable() - Enable tps chip + * + * This function enables TPS chip by associating SYSEN signal + * to APE resource group of TWL6030. + * + * Returns 0 on sucess, error is returned if I2C read/write fails. + */ +static int __init omap4_twl_tps62361_enable(struct voltagedomain *voltdm) +{ + int ret = 0; + u8 val; + + /* Dont trust the bootloader. start with max, pm will set to proper */ + val = voltdm->pmic->uv_to_vsel(voltdm->pmic->vp_vddmax); + ret = omap_vc_bypass_send_i2c_msg(voltdm, voltdm->pmic->i2c_slave_addr, + default_reg, val); + + /* Setup Ramp */ + val = tps6236x_ramp_value(voltdm->pmic->slew_rate) << + __ffs(REG_TPS6236X_RAMP_CTRL_RMP_MASK); + val &= REG_TPS6236X_RAMP_CTRL_RMP_MASK; + + /* We would like to ramp the voltage asap */ + val |= REG_TPS6236X_RAMP_CTRL_RAMP_PFM; + + ret = omap_vc_bypass_send_i2c_msg(voltdm, voltdm->pmic->i2c_slave_addr, + REG_TPS6236X_RAMP_CTRL, val); + if (ret) + goto out; + + /* Setup the internal pulls to select if needed */ + if (pd_vsel0 != -1 || pd_vsel1 != -1) { + val = REG_TPS6236X_CTRL_PD_EN; + val |= (pd_vsel0) ? 0 : REG_TPS6236X_CTRL_PD_VSEL0; + val |= (pd_vsel1) ? 0 : REG_TPS6236X_CTRL_PD_VSEL1; + ret = omap_vc_bypass_send_i2c_msg(voltdm, + voltdm->pmic->i2c_slave_addr, + REG_TPS6236X_CTRL, val); + if (ret) + goto out; + } + + /* Enable thermal shutdown - 0 is enable :) */ + ret = omap_vc_bypass_send_i2c_msg(voltdm, + voltdm->pmic->i2c_slave_addr, + REG_TPS6236X_TEMP, 0x0); + if (ret) + goto out; + + /* if we have to work with TWL */ +#ifdef CONFIG_TWL4030_CORE + ret = twl_i2c_read_u8(TWL4030_MODULE_PM_RECEIVER, &val, + TWL6030_REG_SYSEN_CFG_GRP); + if (ret) + goto out; + + val |= TWL6030_BIT_APE_GRP; + ret = twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, val, + TWL6030_REG_SYSEN_CFG_GRP); +#endif + +out: + if (ret) + pr_err("%s: Error enabling TPS(%d)\n", __func__, ret); + + return ret; +} + +static __initdata struct omap_pmic_map omap_tps_map[] = { + { + .name = "mpu", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP4460), + .pmic_data = &omap4_mpu_pmic, + .special_action = omap4_twl_tps62361_enable, + }, + /* Terminator */ + { .name = NULL,.pmic_data = NULL}, +}; + +int __init omap_tps6236x_init(void) +{ + struct omap_pmic_map *map; + + /* Without registers, I wont proceed */ + if (default_reg == -1) + return -EINVAL; + + map = omap_tps_map; + + /* setup all the pmic's voltage addresses to the default one */ + while (map->name) { + map->pmic_data->volt_reg_addr = default_reg; + map->pmic_data->cmd_reg_addr = default_reg; + map++; + } + + return omap_pmic_register_data(omap_tps_map); +} + +/** + * omap_tps6236x_board_setup() - provide the board config for TPS connect + * @use_62361: Do we use TPS62361 variant? + * @gpio_vsel0: If using GPIO to control VSEL0, provide gpio number, else -1 + * @gpio_vsel1: If using GPIO to control VSEL1, provide gpio number, else -1 + * @pull0: If using GPIO, provide mux mode OMAP_PIN_OFF_OUTPUT_[HIGH|LOW] + * else provide any internal pull required, -1 if unused. + * @pull1: If using GPIO, provide mux mode OMAP_PIN_OFF_OUTPUT_[HIGH|LOW] + * else provide any internal pull required, -1 if unused. + * + * TPS6236x variants of PMIC can be hooked in numerous combinations on to the + * board. Some platforms can choose to hardwire and save on a GPIO for other + * uses, while others may hook a single line for GPIO control and may ground + * the other line. support these configurations. + * + * WARNING: for platforms using GPIO, be careful to provide MUX setting + * considering OFF mode configuration as well. + */ +int __init omap_tps6236x_board_setup(bool use_62361, int gpio_vsel0, + int gpio_vsel1, int pull0, int pull1) +{ + int r; + + r = _bd_setup("tps6236x_vsel0", gpio_vsel0, &pull0, &pd_vsel0); + if (r) + goto out; + r = _bd_setup("tps6236x_vsel1", gpio_vsel1, &pull1, &pd_vsel1); + if (r) { + if (gpio_vsel0 != -1) + gpio_free(gpio_vsel0); + goto out; + } + + default_reg = ((pull1 & 0x1) << 1) | (pull0 & 0x1); + + if (use_62361) { + voltage_min = MIN_VOLTAGE_TPS62361_UV; + voltage_max = MAX_VOLTAGE_TPS62361_UV; + } else { + voltage_min = MIN_VOLTAGE_TPS62360_62_UV; + voltage_max = MAX_VOLTAGE_TPS62360_62_UV; + } +out: + return r; +} diff --git a/arch/arm/mach-omap2/omap_twl.c b/arch/arm/mach-omap2/omap_twl.c index 07d6140baa9d..08d7f9af58a8 100644 --- a/arch/arm/mach-omap2/omap_twl.c +++ b/arch/arm/mach-omap2/omap_twl.c @@ -30,32 +30,13 @@ #define OMAP3_VP_VSTEPMAX_VSTEPMAX 0x04 #define OMAP3_VP_VLIMITTO_TIMEOUT_US 200 -#define OMAP3430_VP1_VLIMITTO_VDDMIN 0x14 -#define OMAP3430_VP1_VLIMITTO_VDDMAX 0x42 -#define OMAP3430_VP2_VLIMITTO_VDDMIN 0x18 -#define OMAP3430_VP2_VLIMITTO_VDDMAX 0x2c - -#define OMAP3630_VP1_VLIMITTO_VDDMIN 0x18 -#define OMAP3630_VP1_VLIMITTO_VDDMAX 0x3c -#define OMAP3630_VP2_VLIMITTO_VDDMIN 0x18 -#define OMAP3630_VP2_VLIMITTO_VDDMAX 0x30 - #define OMAP4_SRI2C_SLAVE_ADDR 0x12 #define OMAP4_VDD_MPU_SR_VOLT_REG 0x55 +#define OMAP4_VDD_MPU_SR_CMD_REG 0x56 #define OMAP4_VDD_IVA_SR_VOLT_REG 0x5B +#define OMAP4_VDD_IVA_SR_CMD_REG 0x5C #define OMAP4_VDD_CORE_SR_VOLT_REG 0x61 - -#define OMAP4_VP_CONFIG_ERROROFFSET 0x00 -#define OMAP4_VP_VSTEPMIN_VSTEPMIN 0x01 -#define OMAP4_VP_VSTEPMAX_VSTEPMAX 0x04 -#define OMAP4_VP_VLIMITTO_TIMEOUT_US 200 - -#define OMAP4_VP_MPU_VLIMITTO_VDDMIN 0xA -#define OMAP4_VP_MPU_VLIMITTO_VDDMAX 0x39 -#define OMAP4_VP_IVA_VLIMITTO_VDDMIN 0xA -#define OMAP4_VP_IVA_VLIMITTO_VDDMAX 0x2D -#define OMAP4_VP_CORE_VLIMITTO_VDDMIN 0xA -#define OMAP4_VP_CORE_VLIMITTO_VDDMAX 0x28 +#define OMAP4_VDD_CORE_SR_CMD_REG 0x62 static bool is_offset_valid; static u8 smps_offset; @@ -95,6 +76,8 @@ static unsigned long twl6030_vsel_to_uv(const u8 vsel) is_offset_valid = true; } + if (!vsel) + return 0; /* * There is no specific formula for voltage to vsel * conversion above 1.3V. There are special hardcoded @@ -106,9 +89,9 @@ static unsigned long twl6030_vsel_to_uv(const u8 vsel) return 1350000; if (smps_offset & 0x8) - return ((((vsel - 1) * 125) + 7000)) * 100; + return ((((vsel - 1) * 1266) + 70900)) * 10; else - return ((((vsel - 1) * 125) + 6000)) * 100; + return ((((vsel - 1) * 1266) + 60770)) * 10; } static u8 twl6030_uv_to_vsel(unsigned long uv) @@ -127,6 +110,8 @@ static u8 twl6030_uv_to_vsel(unsigned long uv) is_offset_valid = true; } + if (!uv) + return 0x00; /* * There is no specific formula for voltage to vsel * conversion above 1.3V. There are special hardcoded @@ -134,16 +119,21 @@ static u8 twl6030_uv_to_vsel(unsigned long uv) * hardcoding only for 1.35 V which is used for 1GH OPP for * OMAP4430. */ - if (uv == 1350000) + if (uv > twl6030_vsel_to_uv(0x39)) { + if (uv == 1350000) + return 0x3A; + pr_err("%s:OUT OF RANGE! non mapped vsel for %ld Vs max %ld\n", + __func__, uv, twl6030_vsel_to_uv(0x39)); return 0x3A; + } if (smps_offset & 0x8) - return DIV_ROUND_UP(uv - 700000, 12500) + 1; + return DIV_ROUND_UP(uv - 709000, 12660) + 1; else - return DIV_ROUND_UP(uv - 600000, 12500) + 1; + return DIV_ROUND_UP(uv - 607700, 12660) + 1; } -static struct omap_volt_pmic_info omap3_mpu_volt_info = { +static struct omap_voltdm_pmic omap3_mpu_pmic = { .slew_rate = 4000, .step_size = 12500, .on_volt = 1200000, @@ -158,12 +148,13 @@ static struct omap_volt_pmic_info omap3_mpu_volt_info = { .vp_vddmax = OMAP3430_VP1_VLIMITTO_VDDMAX, .vp_timeout_us = OMAP3_VP_VLIMITTO_TIMEOUT_US, .i2c_slave_addr = OMAP3_SRI2C_SLAVE_ADDR, - .pmic_reg = OMAP3_VDD_MPU_SR_CONTROL_REG, + .volt_reg_addr = OMAP3_VDD_MPU_SR_CONTROL_REG, + .i2c_high_speed = true, .vsel_to_uv = twl4030_vsel_to_uv, .uv_to_vsel = twl4030_uv_to_vsel, }; -static struct omap_volt_pmic_info omap3_core_volt_info = { +static struct omap_voltdm_pmic omap3_core_pmic = { .slew_rate = 4000, .step_size = 12500, .on_volt = 1200000, @@ -178,18 +169,19 @@ static struct omap_volt_pmic_info omap3_core_volt_info = { .vp_vddmax = OMAP3430_VP2_VLIMITTO_VDDMAX, .vp_timeout_us = OMAP3_VP_VLIMITTO_TIMEOUT_US, .i2c_slave_addr = OMAP3_SRI2C_SLAVE_ADDR, - .pmic_reg = OMAP3_VDD_CORE_SR_CONTROL_REG, + .volt_reg_addr = OMAP3_VDD_CORE_SR_CONTROL_REG, + .i2c_high_speed = true, .vsel_to_uv = twl4030_vsel_to_uv, .uv_to_vsel = twl4030_uv_to_vsel, }; -static struct omap_volt_pmic_info omap4_mpu_volt_info = { +static struct omap_voltdm_pmic omap4_mpu_pmic = { .slew_rate = 4000, - .step_size = 12500, - .on_volt = 1350000, - .onlp_volt = 1350000, - .ret_volt = 837500, - .off_volt = 600000, + .step_size = 12660, + .on_volt = 1375000, + .onlp_volt = 1375000, + .ret_volt = 830000, + .off_volt = 0, .volt_setup_time = 0, .vp_erroroffset = OMAP4_VP_CONFIG_ERROROFFSET, .vp_vstepmin = OMAP4_VP_VSTEPMIN_VSTEPMIN, @@ -198,18 +190,24 @@ static struct omap_volt_pmic_info omap4_mpu_volt_info = { .vp_vddmax = OMAP4_VP_MPU_VLIMITTO_VDDMAX, .vp_timeout_us = OMAP4_VP_VLIMITTO_TIMEOUT_US, .i2c_slave_addr = OMAP4_SRI2C_SLAVE_ADDR, - .pmic_reg = OMAP4_VDD_MPU_SR_VOLT_REG, + .volt_reg_addr = OMAP4_VDD_MPU_SR_VOLT_REG, + .cmd_reg_addr = OMAP4_VDD_MPU_SR_CMD_REG, + .i2c_high_speed = true, + .i2c_scll_low = 0x28, + .i2c_scll_high = 0x2C, + .i2c_hscll_low = 0x0B, + .i2c_hscll_high = 0x00, .vsel_to_uv = twl6030_vsel_to_uv, .uv_to_vsel = twl6030_uv_to_vsel, }; -static struct omap_volt_pmic_info omap4_iva_volt_info = { +static struct omap_voltdm_pmic omap4_iva_pmic = { .slew_rate = 4000, - .step_size = 12500, - .on_volt = 1100000, - .onlp_volt = 1100000, - .ret_volt = 837500, - .off_volt = 600000, + .step_size = 12660, + .on_volt = 1188000, + .onlp_volt = 1188000, + .ret_volt = 830000, + .off_volt = 0, .volt_setup_time = 0, .vp_erroroffset = OMAP4_VP_CONFIG_ERROROFFSET, .vp_vstepmin = OMAP4_VP_VSTEPMIN_VSTEPMIN, @@ -218,18 +216,24 @@ static struct omap_volt_pmic_info omap4_iva_volt_info = { .vp_vddmax = OMAP4_VP_IVA_VLIMITTO_VDDMAX, .vp_timeout_us = OMAP4_VP_VLIMITTO_TIMEOUT_US, .i2c_slave_addr = OMAP4_SRI2C_SLAVE_ADDR, - .pmic_reg = OMAP4_VDD_IVA_SR_VOLT_REG, + .volt_reg_addr = OMAP4_VDD_IVA_SR_VOLT_REG, + .cmd_reg_addr = OMAP4_VDD_IVA_SR_CMD_REG, + .i2c_high_speed = true, + .i2c_scll_low = 0x28, + .i2c_scll_high = 0x2C, + .i2c_hscll_low = 0x0B, + .i2c_hscll_high = 0x00, .vsel_to_uv = twl6030_vsel_to_uv, .uv_to_vsel = twl6030_uv_to_vsel, }; -static struct omap_volt_pmic_info omap4_core_volt_info = { +static struct omap_voltdm_pmic omap443x_core_pmic = { .slew_rate = 4000, - .step_size = 12500, - .on_volt = 1100000, - .onlp_volt = 1100000, - .ret_volt = 837500, - .off_volt = 600000, + .step_size = 12660, + .on_volt = 1200000, + .onlp_volt = 1200000, + .ret_volt = 830000, + .off_volt = 0, .volt_setup_time = 0, .vp_erroroffset = OMAP4_VP_CONFIG_ERROROFFSET, .vp_vstepmin = OMAP4_VP_VSTEPMIN_VSTEPMIN, @@ -238,43 +242,47 @@ static struct omap_volt_pmic_info omap4_core_volt_info = { .vp_vddmax = OMAP4_VP_CORE_VLIMITTO_VDDMAX, .vp_timeout_us = OMAP4_VP_VLIMITTO_TIMEOUT_US, .i2c_slave_addr = OMAP4_SRI2C_SLAVE_ADDR, - .pmic_reg = OMAP4_VDD_CORE_SR_VOLT_REG, + .i2c_high_speed = true, + .i2c_scll_low = 0x28, + .i2c_scll_high = 0x2C, + .i2c_hscll_low = 0x0B, + .i2c_hscll_high = 0x00, + .volt_reg_addr = OMAP4_VDD_CORE_SR_VOLT_REG, + .cmd_reg_addr = OMAP4_VDD_CORE_SR_CMD_REG, .vsel_to_uv = twl6030_vsel_to_uv, .uv_to_vsel = twl6030_uv_to_vsel, }; -int __init omap4_twl_init(void) -{ - struct voltagedomain *voltdm; - - if (!cpu_is_omap44xx()) - return -ENODEV; - - voltdm = omap_voltage_domain_lookup("mpu"); - omap_voltage_register_pmic(voltdm, &omap4_mpu_volt_info); - - voltdm = omap_voltage_domain_lookup("iva"); - omap_voltage_register_pmic(voltdm, &omap4_iva_volt_info); - - voltdm = omap_voltage_domain_lookup("core"); - omap_voltage_register_pmic(voltdm, &omap4_core_volt_info); - - return 0; -} +/* Core uses the MPU rail of 4430 */ +static struct omap_voltdm_pmic omap446x_core_pmic = { + .slew_rate = 4000, + .step_size = 12660, + .on_volt = 1200000, + .onlp_volt = 1200000, + .ret_volt = 830000, + .off_volt = 0, + .volt_setup_time = 0, + .vp_erroroffset = OMAP4_VP_CONFIG_ERROROFFSET, + .vp_vstepmin = OMAP4_VP_VSTEPMIN_VSTEPMIN, + .vp_vstepmax = OMAP4_VP_VSTEPMAX_VSTEPMAX, + .vp_vddmin = OMAP4_VP_CORE_VLIMITTO_VDDMIN, + .vp_vddmax = OMAP4_VP_CORE_VLIMITTO_VDDMAX, + .vp_timeout_us = OMAP4_VP_VLIMITTO_TIMEOUT_US, + .i2c_slave_addr = OMAP4_SRI2C_SLAVE_ADDR, + .i2c_high_speed = true, + .i2c_scll_low = 0x28, + .i2c_scll_high = 0x2C, + .i2c_hscll_low = 0x0B, + .i2c_hscll_high = 0x00, + .volt_reg_addr = OMAP4_VDD_MPU_SR_VOLT_REG, + .cmd_reg_addr = OMAP4_VDD_MPU_SR_CMD_REG, + .vsel_to_uv = twl6030_vsel_to_uv, + .uv_to_vsel = twl6030_uv_to_vsel, +}; -int __init omap3_twl_init(void) +static int __init twl_set_sr(struct voltagedomain *voltdm) { - struct voltagedomain *voltdm; - - if (!cpu_is_omap34xx()) - return -ENODEV; - - if (cpu_is_omap3630()) { - omap3_mpu_volt_info.vp_vddmin = OMAP3630_VP1_VLIMITTO_VDDMIN; - omap3_mpu_volt_info.vp_vddmax = OMAP3630_VP1_VLIMITTO_VDDMAX; - omap3_core_volt_info.vp_vddmin = OMAP3630_VP2_VLIMITTO_VDDMIN; - omap3_core_volt_info.vp_vddmax = OMAP3630_VP2_VLIMITTO_VDDMAX; - } + int r = 0; /* * The smartreflex bit on twl4030 specifies if the setting of voltage @@ -286,15 +294,57 @@ int __init omap3_twl_init(void) * voltage scaling will not function on TWL over I2C_SR. */ if (!twl_sr_enable_autoinit) - omap3_twl_set_sr_bit(true); + r = omap3_twl_set_sr_bit(true); + return r; +} - voltdm = omap_voltage_domain_lookup("mpu"); - omap_voltage_register_pmic(voltdm, &omap3_mpu_volt_info); +static __initdata struct omap_pmic_map omap_twl_map[] = { + { + .name = "mpu", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .pmic_data = &omap3_mpu_pmic, + .special_action = twl_set_sr, + }, + { + .name = "core", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .pmic_data = &omap3_core_pmic, + }, + { + .name = "mpu", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP4430), + .pmic_data = &omap4_mpu_pmic, + }, + { + .name = "core", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP4430), + .pmic_data = &omap443x_core_pmic, + }, + { + .name = "core", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP4460), + .pmic_data = &omap446x_core_pmic, + }, + { + .name = "iva", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), + .pmic_data = &omap4_iva_pmic, + }, + /* Terminator */ + { .name = NULL,.pmic_data = NULL}, +}; - voltdm = omap_voltage_domain_lookup("core"); - omap_voltage_register_pmic(voltdm, &omap3_core_volt_info); +int __init omap_twl_init(void) +{ + /* Reuse OMAP3430 values */ + if (cpu_is_omap3630()) { + omap3_mpu_pmic.vp_vddmin = OMAP3630_VP1_VLIMITTO_VDDMIN; + omap3_mpu_pmic.vp_vddmax = OMAP3630_VP1_VLIMITTO_VDDMAX; + omap3_core_pmic.vp_vddmin = OMAP3630_VP2_VLIMITTO_VDDMIN; + omap3_core_pmic.vp_vddmax = OMAP3630_VP2_VLIMITTO_VDDMAX; + } - return 0; + return omap_pmic_register_data(omap_twl_map); } /** diff --git a/arch/arm/mach-omap2/opp.c b/arch/arm/mach-omap2/opp.c index ab8b35b780b5..3f423bffef90 100644 --- a/arch/arm/mach-omap2/opp.c +++ b/arch/arm/mach-omap2/opp.c @@ -22,6 +22,7 @@ #include <plat/omap_device.h> #include "omap_opp_data.h" +#include "dvfs.h" /* Temp variable to allow multiple calls */ static u8 __initdata omap_table_init; @@ -85,6 +86,12 @@ int __init omap_init_opp_table(struct omap_opp_def *opp_def, "[%d] result=%d\n", __func__, opp_def->freq, opp_def->hwmod_name, i, r); + + r = omap_dvfs_register_device(dev, + opp_def->voltdm_name, opp_def->clk_name); + if (r) + dev_err(dev, "%s:%s:err dvfs register %d %d\n", + __func__, opp_def->hwmod_name, r, i); } opp_def++; } diff --git a/arch/arm/mach-omap2/opp3xxx_data.c b/arch/arm/mach-omap2/opp3xxx_data.c index d95f3f945d4a..7662662beff9 100644 --- a/arch/arm/mach-omap2/opp3xxx_data.c +++ b/arch/arm/mach-omap2/opp3xxx_data.c @@ -57,6 +57,24 @@ struct omap_volt_data omap34xx_vddcore_volt_data[] = { VOLT_DATA_DEFINE(0, 0, 0, 0), }; +/* OMAP 3430 MPU Core VDD dependency table */ +static struct omap_vdd_dep_volt omap34xx_vdd_mpu_core_dep_data[] = { + {.main_vdd_volt = OMAP3430_VDD_MPU_OPP1_UV, .dep_vdd_volt = OMAP3430_VDD_CORE_OPP2_UV}, + {.main_vdd_volt = OMAP3430_VDD_MPU_OPP2_UV, .dep_vdd_volt = OMAP3430_VDD_CORE_OPP2_UV}, + {.main_vdd_volt = OMAP3430_VDD_MPU_OPP3_UV, .dep_vdd_volt = OMAP3430_VDD_CORE_OPP3_UV}, + {.main_vdd_volt = OMAP3430_VDD_MPU_OPP4_UV, .dep_vdd_volt = OMAP3430_VDD_CORE_OPP3_UV}, + {.main_vdd_volt = OMAP3430_VDD_MPU_OPP5_UV, .dep_vdd_volt = OMAP3430_VDD_CORE_OPP3_UV}, +}; + +struct omap_vdd_dep_info omap34xx_vddmpu_dep_info[] = { + { + .name = "core", + .dep_table = omap34xx_vdd_mpu_core_dep_data, + .nr_dep_entries = ARRAY_SIZE(omap34xx_vdd_mpu_core_dep_data), + }, + {.name = NULL, .dep_table = NULL, .nr_dep_entries = 0}, +}; + /* 36xx */ /* VDD1 */ @@ -89,15 +107,15 @@ struct omap_volt_data omap36xx_vddcore_volt_data[] = { static struct omap_opp_def __initdata omap34xx_opp_def_list[] = { /* MPU OPP1 */ - OPP_INITIALIZER("mpu", true, 125000000, OMAP3430_VDD_MPU_OPP1_UV), + OPP_INITIALIZER("mpu", "dpll1_ck", "mpu_iva", true, 125000000, OMAP3430_VDD_MPU_OPP1_UV), /* MPU OPP2 */ - OPP_INITIALIZER("mpu", true, 250000000, OMAP3430_VDD_MPU_OPP2_UV), + OPP_INITIALIZER("mpu", "dpll1_ck", "mpu_iva", true, 250000000, OMAP3430_VDD_MPU_OPP2_UV), /* MPU OPP3 */ - OPP_INITIALIZER("mpu", true, 500000000, OMAP3430_VDD_MPU_OPP3_UV), + OPP_INITIALIZER("mpu", "dpll1_ck", "mpu_iva", true, 500000000, OMAP3430_VDD_MPU_OPP3_UV), /* MPU OPP4 */ - OPP_INITIALIZER("mpu", true, 550000000, OMAP3430_VDD_MPU_OPP4_UV), + OPP_INITIALIZER("mpu", "dpll1_ck", "mpu_iva", true, 550000000, OMAP3430_VDD_MPU_OPP4_UV), /* MPU OPP5 */ - OPP_INITIALIZER("mpu", true, 600000000, OMAP3430_VDD_MPU_OPP5_UV), + OPP_INITIALIZER("mpu", "dpll1_ck", "mpu_iva", true, 600000000, OMAP3430_VDD_MPU_OPP5_UV), /* * L3 OPP1 - 41.5 MHz is disabled because: The voltage for that OPP is @@ -107,47 +125,64 @@ static struct omap_opp_def __initdata omap34xx_opp_def_list[] = { * impact that frequency will do to the MPU and the whole system in * general. */ - OPP_INITIALIZER("l3_main", false, 41500000, OMAP3430_VDD_CORE_OPP1_UV), + OPP_INITIALIZER("l3_main", "dpll3_ck", "core", false, 41500000, OMAP3430_VDD_CORE_OPP1_UV), /* L3 OPP2 */ - OPP_INITIALIZER("l3_main", true, 83000000, OMAP3430_VDD_CORE_OPP2_UV), + OPP_INITIALIZER("l3_main", "dpll3_ck", "core", true, 83000000, OMAP3430_VDD_CORE_OPP2_UV), /* L3 OPP3 */ - OPP_INITIALIZER("l3_main", true, 166000000, OMAP3430_VDD_CORE_OPP3_UV), + OPP_INITIALIZER("l3_main", "dpll3_ck", "core", true, 166000000, OMAP3430_VDD_CORE_OPP3_UV), /* DSP OPP1 */ - OPP_INITIALIZER("iva", true, 90000000, OMAP3430_VDD_MPU_OPP1_UV), + OPP_INITIALIZER("iva", "dpll2_ck", "mpu_iva", true, 90000000, OMAP3430_VDD_MPU_OPP1_UV), /* DSP OPP2 */ - OPP_INITIALIZER("iva", true, 180000000, OMAP3430_VDD_MPU_OPP2_UV), + OPP_INITIALIZER("iva", "dpll2_ck", "mpu_iva", true, 180000000, OMAP3430_VDD_MPU_OPP2_UV), /* DSP OPP3 */ - OPP_INITIALIZER("iva", true, 360000000, OMAP3430_VDD_MPU_OPP3_UV), + OPP_INITIALIZER("iva", "dpll2_ck", "mpu_iva", true, 360000000, OMAP3430_VDD_MPU_OPP3_UV), /* DSP OPP4 */ - OPP_INITIALIZER("iva", true, 400000000, OMAP3430_VDD_MPU_OPP4_UV), + OPP_INITIALIZER("iva", "dpll2_ck", "mpu_iva", true, 400000000, OMAP3430_VDD_MPU_OPP4_UV), /* DSP OPP5 */ - OPP_INITIALIZER("iva", true, 430000000, OMAP3430_VDD_MPU_OPP5_UV), + OPP_INITIALIZER("iva", "dpll2_ck", "mpu_iva", true, 430000000, OMAP3430_VDD_MPU_OPP5_UV), }; static struct omap_opp_def __initdata omap36xx_opp_def_list[] = { /* MPU OPP1 - OPP50 */ - OPP_INITIALIZER("mpu", true, 300000000, OMAP3630_VDD_MPU_OPP50_UV), + OPP_INITIALIZER("mpu", "dpll1_ck", "mpu_iva", true, 300000000, OMAP3630_VDD_MPU_OPP50_UV), /* MPU OPP2 - OPP100 */ - OPP_INITIALIZER("mpu", true, 600000000, OMAP3630_VDD_MPU_OPP100_UV), + OPP_INITIALIZER("mpu", "dpll1_ck", "mpu_iva", true, 600000000, OMAP3630_VDD_MPU_OPP100_UV), /* MPU OPP3 - OPP-Turbo */ - OPP_INITIALIZER("mpu", false, 800000000, OMAP3630_VDD_MPU_OPP120_UV), + OPP_INITIALIZER("mpu", "dpll1_ck", "mpu_iva", false, 800000000, OMAP3630_VDD_MPU_OPP120_UV), /* MPU OPP4 - OPP-SB */ - OPP_INITIALIZER("mpu", false, 1000000000, OMAP3630_VDD_MPU_OPP1G_UV), + OPP_INITIALIZER("mpu", "dpll1_ck", "mpu_iva", false, 1000000000, OMAP3630_VDD_MPU_OPP1G_UV), /* L3 OPP1 - OPP50 */ - OPP_INITIALIZER("l3_main", true, 100000000, OMAP3630_VDD_CORE_OPP50_UV), + OPP_INITIALIZER("l3_main", "dpll3_ck", "core", true, 100000000, OMAP3630_VDD_CORE_OPP50_UV), /* L3 OPP2 - OPP100, OPP-Turbo, OPP-SB */ - OPP_INITIALIZER("l3_main", true, 200000000, OMAP3630_VDD_CORE_OPP100_UV), + OPP_INITIALIZER("l3_main", "dpll3_ck", "core", true, 200000000, OMAP3630_VDD_CORE_OPP100_UV), /* DSP OPP1 - OPP50 */ - OPP_INITIALIZER("iva", true, 260000000, OMAP3630_VDD_MPU_OPP50_UV), + OPP_INITIALIZER("iva", "dpll2_ck", "mpu_iva", true, 260000000, OMAP3630_VDD_MPU_OPP50_UV), /* DSP OPP2 - OPP100 */ - OPP_INITIALIZER("iva", true, 520000000, OMAP3630_VDD_MPU_OPP100_UV), + OPP_INITIALIZER("iva", "dpll2_ck", "mpu_iva", true, 520000000, OMAP3630_VDD_MPU_OPP100_UV), /* DSP OPP3 - OPP-Turbo */ - OPP_INITIALIZER("iva", false, 660000000, OMAP3630_VDD_MPU_OPP120_UV), + OPP_INITIALIZER("iva", "dpll2_ck", "mpu_iva", false, 660000000, OMAP3630_VDD_MPU_OPP120_UV), /* DSP OPP4 - OPP-SB */ - OPP_INITIALIZER("iva", false, 800000000, OMAP3630_VDD_MPU_OPP1G_UV), + OPP_INITIALIZER("iva", "dpll2_ck", "mpu_iva", false, 800000000, OMAP3630_VDD_MPU_OPP1G_UV), +}; + +/* OMAP 3630 MPU Core VDD dependency table */ +static struct omap_vdd_dep_volt omap36xx_vdd_mpu_core_dep_data[] = { + {.main_vdd_volt = OMAP3630_VDD_MPU_OPP50_UV, .dep_vdd_volt = OMAP3630_VDD_CORE_OPP50_UV}, + {.main_vdd_volt = OMAP3630_VDD_MPU_OPP100_UV, .dep_vdd_volt = OMAP3630_VDD_CORE_OPP100_UV}, + {.main_vdd_volt = OMAP3630_VDD_MPU_OPP120_UV, .dep_vdd_volt = OMAP3630_VDD_CORE_OPP100_UV}, + {.main_vdd_volt = OMAP3630_VDD_MPU_OPP1G_UV, .dep_vdd_volt = OMAP3630_VDD_CORE_OPP100_UV}, +}; + +struct omap_vdd_dep_info omap36xx_vddmpu_dep_info[] = { + { + .name = "core", + .dep_table = omap36xx_vdd_mpu_core_dep_data, + .nr_dep_entries = ARRAY_SIZE(omap36xx_vdd_mpu_core_dep_data), + }, + {.name = NULL, .dep_table = NULL, .nr_dep_entries = 0}, }; /** diff --git a/arch/arm/mach-omap2/opp4xxx_data.c b/arch/arm/mach-omap2/opp4xxx_data.c index 2293ba27101b..bc2c60f28fb9 100644 --- a/arch/arm/mach-omap2/opp4xxx_data.c +++ b/arch/arm/mach-omap2/opp4xxx_data.c @@ -1,7 +1,7 @@ /* * OMAP4 OPP table definitions. * - * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com/ + * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com/ * Nishanth Menon * Kevin Hilman * Thara Gopinath @@ -36,7 +36,7 @@ #define OMAP4430_VDD_MPU_OPPTURBO_UV 1313000 #define OMAP4430_VDD_MPU_OPPNITRO_UV 1375000 -struct omap_volt_data omap44xx_vdd_mpu_volt_data[] = { +struct omap_volt_data omap443x_vdd_mpu_volt_data[] = { VOLT_DATA_DEFINE(OMAP4430_VDD_MPU_OPP50_UV, OMAP44XX_CONTROL_FUSE_MPU_OPP50, 0xf4, 0x0c), VOLT_DATA_DEFINE(OMAP4430_VDD_MPU_OPP100_UV, OMAP44XX_CONTROL_FUSE_MPU_OPP100, 0xf9, 0x16), VOLT_DATA_DEFINE(OMAP4430_VDD_MPU_OPPTURBO_UV, OMAP44XX_CONTROL_FUSE_MPU_OPPTURBO, 0xfa, 0x23), @@ -48,7 +48,7 @@ struct omap_volt_data omap44xx_vdd_mpu_volt_data[] = { #define OMAP4430_VDD_IVA_OPP100_UV 1188000 #define OMAP4430_VDD_IVA_OPPTURBO_UV 1300000 -struct omap_volt_data omap44xx_vdd_iva_volt_data[] = { +struct omap_volt_data omap443x_vdd_iva_volt_data[] = { VOLT_DATA_DEFINE(OMAP4430_VDD_IVA_OPP50_UV, OMAP44XX_CONTROL_FUSE_IVA_OPP50, 0xf4, 0x0c), VOLT_DATA_DEFINE(OMAP4430_VDD_IVA_OPP100_UV, OMAP44XX_CONTROL_FUSE_IVA_OPP100, 0xf9, 0x16), VOLT_DATA_DEFINE(OMAP4430_VDD_IVA_OPPTURBO_UV, OMAP44XX_CONTROL_FUSE_IVA_OPPTURBO, 0xfa, 0x23), @@ -58,32 +58,149 @@ struct omap_volt_data omap44xx_vdd_iva_volt_data[] = { #define OMAP4430_VDD_CORE_OPP50_UV 1025000 #define OMAP4430_VDD_CORE_OPP100_UV 1200000 -struct omap_volt_data omap44xx_vdd_core_volt_data[] = { +struct omap_volt_data omap443x_vdd_core_volt_data[] = { VOLT_DATA_DEFINE(OMAP4430_VDD_CORE_OPP50_UV, OMAP44XX_CONTROL_FUSE_CORE_OPP50, 0xf4, 0x0c), VOLT_DATA_DEFINE(OMAP4430_VDD_CORE_OPP100_UV, OMAP44XX_CONTROL_FUSE_CORE_OPP100, 0xf9, 0x16), VOLT_DATA_DEFINE(0, 0, 0, 0), }; +/* Dependency of domains are as follows for OMAP4430 (OPP based): + * + * MPU IVA CORE + * 50 50 50+ + * 50 100+ 100 + * 100+ 50 100 + * 100+ 100+ 100 + */ -static struct omap_opp_def __initdata omap44xx_opp_def_list[] = { +/* OMAP 4430 MPU Core VDD dependency table */ +static struct omap_vdd_dep_volt omap443x_vdd_mpu_core_dep_data[] = { + {.main_vdd_volt = OMAP4430_VDD_MPU_OPP50_UV, .dep_vdd_volt = OMAP4430_VDD_MPU_OPP50_UV}, + {.main_vdd_volt = OMAP4430_VDD_MPU_OPP100_UV, .dep_vdd_volt = OMAP4430_VDD_CORE_OPP100_UV}, + {.main_vdd_volt = OMAP4430_VDD_MPU_OPPTURBO_UV, .dep_vdd_volt = OMAP4430_VDD_CORE_OPP100_UV}, + {.main_vdd_volt = OMAP4430_VDD_MPU_OPPNITRO_UV, .dep_vdd_volt = OMAP4430_VDD_CORE_OPP100_UV}, +}; + +struct omap_vdd_dep_info omap443x_vddmpu_dep_info[] = { + { + .name = "core", + .dep_table = omap443x_vdd_mpu_core_dep_data, + .nr_dep_entries = ARRAY_SIZE(omap443x_vdd_mpu_core_dep_data), + }, + {.name = NULL, .dep_table = NULL, .nr_dep_entries = 0}, +}; + +/* OMAP 4430 MPU IVA VDD dependency table */ +static struct omap_vdd_dep_volt omap443x_vdd_iva_core_dep_data[] = { + {.main_vdd_volt = OMAP4430_VDD_IVA_OPP50_UV, .dep_vdd_volt = OMAP4430_VDD_MPU_OPP50_UV}, + {.main_vdd_volt = OMAP4430_VDD_IVA_OPP100_UV, .dep_vdd_volt = OMAP4430_VDD_CORE_OPP100_UV}, + {.main_vdd_volt = OMAP4430_VDD_IVA_OPPTURBO_UV, .dep_vdd_volt = OMAP4430_VDD_CORE_OPP100_UV}, +}; + +struct omap_vdd_dep_info omap443x_vddiva_dep_info[] = { + { + .name = "core", + .dep_table = omap443x_vdd_iva_core_dep_data, + .nr_dep_entries = ARRAY_SIZE(omap443x_vdd_iva_core_dep_data), + }, + {.name = NULL, .dep_table = NULL, .nr_dep_entries = 0}, +}; + +static struct omap_opp_def __initdata omap443x_opp_def_list[] = { /* MPU OPP1 - OPP50 */ - OPP_INITIALIZER("mpu", true, 300000000, OMAP4430_VDD_MPU_OPP50_UV), + OPP_INITIALIZER("mpu", "dpll_mpu_ck", "mpu", true, 300000000, OMAP4430_VDD_MPU_OPP50_UV), /* MPU OPP2 - OPP100 */ - OPP_INITIALIZER("mpu", true, 600000000, OMAP4430_VDD_MPU_OPP100_UV), + OPP_INITIALIZER("mpu", "dpll_mpu_ck", "mpu", true, 600000000, OMAP4430_VDD_MPU_OPP100_UV), /* MPU OPP3 - OPP-Turbo */ - OPP_INITIALIZER("mpu", true, 800000000, OMAP4430_VDD_MPU_OPPTURBO_UV), + OPP_INITIALIZER("mpu", "dpll_mpu_ck", "mpu", true, 800000000, OMAP4430_VDD_MPU_OPPTURBO_UV), /* MPU OPP4 - OPP-SB */ - OPP_INITIALIZER("mpu", true, 1008000000, OMAP4430_VDD_MPU_OPPNITRO_UV), + OPP_INITIALIZER("mpu", "dpll_mpu_ck", "mpu", true, 1008000000, OMAP4430_VDD_MPU_OPPNITRO_UV), /* L3 OPP1 - OPP50 */ - OPP_INITIALIZER("l3_main_1", true, 100000000, OMAP4430_VDD_CORE_OPP50_UV), + OPP_INITIALIZER("l3_main_1", "dpll_core_m5x2_ck", "core", true, 100000000, OMAP4430_VDD_CORE_OPP50_UV), /* L3 OPP2 - OPP100, OPP-Turbo, OPP-SB */ - OPP_INITIALIZER("l3_main_1", true, 200000000, OMAP4430_VDD_CORE_OPP100_UV), + OPP_INITIALIZER("l3_main_1", "dpll_core_m5x2_ck", "core", true, 200000000, OMAP4430_VDD_CORE_OPP100_UV), /* IVA OPP1 - OPP50 */ - OPP_INITIALIZER("iva", true, 133000000, OMAP4430_VDD_IVA_OPP50_UV), + OPP_INITIALIZER("iva", "dpll_iva_m5x2_ck", "iva", true, 133000000, OMAP4430_VDD_IVA_OPP50_UV), /* IVA OPP2 - OPP100 */ - OPP_INITIALIZER("iva", true, 266100000, OMAP4430_VDD_IVA_OPP100_UV), + OPP_INITIALIZER("iva", "dpll_iva_m5x2_ck", "iva", true, 266100000, OMAP4430_VDD_IVA_OPP100_UV), /* IVA OPP3 - OPP-Turbo */ - OPP_INITIALIZER("iva", false, 332000000, OMAP4430_VDD_IVA_OPPTURBO_UV), + OPP_INITIALIZER("iva", "dpll_iva_m5x2_ck", "iva", false, 332000000, OMAP4430_VDD_IVA_OPPTURBO_UV), + /* TODO: add DSP, aess, fdif, gpu */ +}; + +#define OMAP4460_VDD_MPU_OPP50_UV 1025000 +#define OMAP4460_VDD_MPU_OPP100_UV 1200000 +#define OMAP4460_VDD_MPU_OPPTURBO_UV 1313000 +#define OMAP4460_VDD_MPU_OPPNITRO_UV 1375000 + +struct omap_volt_data omap446x_vdd_mpu_volt_data[] = { + VOLT_DATA_DEFINE(OMAP4460_VDD_MPU_OPP50_UV, OMAP44XX_CONTROL_FUSE_MPU_OPP50, 0xf4, 0x0c), + VOLT_DATA_DEFINE(OMAP4460_VDD_MPU_OPP100_UV, OMAP44XX_CONTROL_FUSE_MPU_OPP100, 0xf9, 0x16), + VOLT_DATA_DEFINE(OMAP4460_VDD_MPU_OPPTURBO_UV, OMAP44XX_CONTROL_FUSE_MPU_OPPTURBO, 0xfa, 0x23), + VOLT_DATA_DEFINE(OMAP4460_VDD_MPU_OPPNITRO_UV, OMAP44XX_CONTROL_FUSE_MPU_OPPNITRO, 0xfa, 0x27), + VOLT_DATA_DEFINE(0, 0, 0, 0), +}; + +#define OMAP4460_VDD_IVA_OPP50_UV 1025000 +#define OMAP4460_VDD_IVA_OPP100_UV 1200000 +#define OMAP4460_VDD_IVA_OPPTURBO_UV 1313000 +#define OMAP4460_VDD_IVA_OPPNITRO_UV 1375000 + +struct omap_volt_data omap446x_vdd_iva_volt_data[] = { + VOLT_DATA_DEFINE(OMAP4460_VDD_IVA_OPP50_UV, OMAP44XX_CONTROL_FUSE_IVA_OPP50, 0xf4, 0x0c), + VOLT_DATA_DEFINE(OMAP4460_VDD_IVA_OPP100_UV, OMAP44XX_CONTROL_FUSE_IVA_OPP100, 0xf9, 0x16), + VOLT_DATA_DEFINE(OMAP4460_VDD_IVA_OPPTURBO_UV, OMAP44XX_CONTROL_FUSE_IVA_OPPTURBO, 0xfa, 0x23), + VOLT_DATA_DEFINE(OMAP4460_VDD_IVA_OPPNITRO_UV, OMAP44XX_CONTROL_FUSE_IVA_OPPNITRO, 0xfa, 0x23), + VOLT_DATA_DEFINE(0, 0, 0, 0), +}; + +#define OMAP4460_VDD_CORE_OPP50_UV 1025000 +#define OMAP4460_VDD_CORE_OPP100_UV 1200000 +#define OMAP4460_VDD_CORE_OPP100_OV_UV 1250000 + +struct omap_volt_data omap446x_vdd_core_volt_data[] = { + VOLT_DATA_DEFINE(OMAP4460_VDD_CORE_OPP50_UV, OMAP44XX_CONTROL_FUSE_CORE_OPP50, 0xf4, 0x0c), + VOLT_DATA_DEFINE(OMAP4460_VDD_CORE_OPP100_UV, OMAP44XX_CONTROL_FUSE_CORE_OPP100, 0xf9, 0x16), + VOLT_DATA_DEFINE(OMAP4460_VDD_CORE_OPP100_OV_UV, OMAP44XX_CONTROL_FUSE_CORE_OPP100OV, 0xf9, 0x16), + VOLT_DATA_DEFINE(0, 0, 0, 0), +}; + +static struct omap_opp_def __initdata omap446x_opp_def_list[] = { + /* MPU OPP1 - OPP50 */ + OPP_INITIALIZER("mpu", "virt_dpll_mpu_ck", "mpu", true, 350000000, OMAP4460_VDD_MPU_OPP50_UV), + /* MPU OPP2 - OPP100 */ + OPP_INITIALIZER("mpu", "virt_dpll_mpu_ck", "mpu", true, 700000000, OMAP4460_VDD_MPU_OPP100_UV), + /* MPU OPP3 - OPP-Turbo */ + OPP_INITIALIZER("mpu", "virt_dpll_mpu_ck", "mpu", true, 920000000, OMAP4460_VDD_MPU_OPPTURBO_UV), + /* + * MPU OPP4 - OPP-Nitro + Disabled as the reference schematics + * recommends TPS623631 - confirm and enable the opp in board file + * XXX: May be we should enable these based on mpu capability and + * Exception board files disable it... + */ + OPP_INITIALIZER("mpu", "virt_dpll_mpu_ck", "mpu", false, 1200000000, OMAP4460_VDD_MPU_OPPNITRO_UV), + /* MPU OPP4 - OPP-Nitro SpeedBin */ + OPP_INITIALIZER("mpu", "virt_dpll_mpu_ck", "mpu", false, 1500000000, OMAP4460_VDD_MPU_OPPNITRO_UV), + /* L3 OPP1 - OPP50 */ + OPP_INITIALIZER("l3_main_1", "dpll_core_m5x2_ck", "core", true, 100000000, OMAP4460_VDD_CORE_OPP50_UV), + /* L3 OPP2 - OPP100 */ + OPP_INITIALIZER("l3_main_1", "dpll_core_m5x2_ck", "core", true, 200000000, OMAP4460_VDD_CORE_OPP100_UV), + /* IVA OPP1 - OPP50 */ + OPP_INITIALIZER("iva", "dpll_iva_m5x2_ck", "iva", true, 133000000, OMAP4460_VDD_IVA_OPP50_UV), + /* IVA OPP2 - OPP100 */ + OPP_INITIALIZER("iva", "dpll_iva_m5x2_ck", "iva", true, 266100000, OMAP4460_VDD_IVA_OPP100_UV), + /* + * IVA OPP3 - OPP-Turbo + Disabled as the reference schematics + * recommends Phoenix VCORE2 which can supply only 600mA - so the ones + * above this OPP frequency, even though OMAP is capable, should be + * enabled by board file which is sure of the chip power capability + */ + OPP_INITIALIZER("iva", "dpll_iva_m5x2_ck", "iva", false, 332000000, OMAP4460_VDD_IVA_OPPTURBO_UV), + /* IVA OPP4 - OPP-Nitro */ + OPP_INITIALIZER("iva", "dpll_iva_m5x2_ck", "iva", false, 430000000, OMAP4460_VDD_IVA_OPPNITRO_UV), + /* IVA OPP5 - OPP-Nitro SpeedBin*/ + OPP_INITIALIZER("iva", "dpll_iva_m5x2_ck", "iva", false, 500000000, OMAP4460_VDD_IVA_OPPNITRO_UV), + /* TODO: add DSP, aess, fdif, gpu */ }; @@ -96,10 +213,12 @@ int __init omap4_opp_init(void) if (!cpu_is_omap44xx()) return r; - - r = omap_init_opp_table(omap44xx_opp_def_list, - ARRAY_SIZE(omap44xx_opp_def_list)); - + if (cpu_is_omap443x()) + r = omap_init_opp_table(omap443x_opp_def_list, + ARRAY_SIZE(omap443x_opp_def_list)); + else if (cpu_is_omap446x()) + r = omap_init_opp_table(omap446x_opp_def_list, + ARRAY_SIZE(omap446x_opp_def_list)); return r; } device_initcall(omap4_opp_init); diff --git a/arch/arm/mach-omap2/pm-debug.c b/arch/arm/mach-omap2/pm-debug.c index a5a83b358ddd..0ef8d698b34d 100644 --- a/arch/arm/mach-omap2/pm-debug.c +++ b/arch/arm/mach-omap2/pm-debug.c @@ -189,7 +189,7 @@ static struct dentry *pm_dbg_dir; static int pm_dbg_init_done; -static int __init pm_dbg_init(void); +static int pm_dbg_init(void); enum { DEBUG_FILE_COUNTERS = 0, @@ -595,7 +595,7 @@ static int option_set(void *data, u64 val) DEFINE_SIMPLE_ATTRIBUTE(pm_dbg_option_fops, option_get, option_set, "%llu\n"); -static int __init pm_dbg_init(void) +static int pm_dbg_init(void) { int i; struct dentry *d; @@ -604,9 +604,11 @@ static int __init pm_dbg_init(void) if (pm_dbg_init_done) return 0; - if (cpu_is_omap34xx()) + if (cpu_is_omap34xx()) { pm_dbg_reg_modules = omap3_pm_reg_modules; - else { + } else if (cpu_is_omap44xx()) { + /* Allow pm_dbg_init on OMAP4. */ + } else { printk(KERN_ERR "%s: only OMAP3 supported\n", __func__); return -ENODEV; } @@ -622,6 +624,9 @@ static int __init pm_dbg_init(void) pwrdm_for_each(pwrdms_setup, (void *)d); + if (cpu_is_omap44xx()) + goto skip_reg_debufs; + pm_dbg_dir = debugfs_create_dir("registers", d); if (IS_ERR(pm_dbg_dir)) return PTR_ERR(pm_dbg_dir); @@ -641,6 +646,8 @@ static int __init pm_dbg_init(void) &enable_off_mode, &pm_dbg_option_fops); (void) debugfs_create_file("sleep_while_idle", S_IRUGO | S_IWUSR, d, &sleep_while_idle, &pm_dbg_option_fops); + +skip_reg_debufs: (void) debugfs_create_file("wakeup_timer_seconds", S_IRUGO | S_IWUSR, d, &wakeup_timer_seconds, &pm_dbg_option_fops); (void) debugfs_create_file("wakeup_timer_milliseconds", diff --git a/arch/arm/mach-omap2/pm.c b/arch/arm/mach-omap2/pm.c index 49486f522dca..05409c83faf0 100644 --- a/arch/arm/mach-omap2/pm.c +++ b/arch/arm/mach-omap2/pm.c @@ -36,18 +36,21 @@ struct device *omap2_get_mpuss_device(void) WARN_ON_ONCE(!mpu_dev); return mpu_dev; } +EXPORT_SYMBOL(omap2_get_mpuss_device); struct device *omap2_get_iva_device(void) { WARN_ON_ONCE(!iva_dev); return iva_dev; } +EXPORT_SYMBOL(omap2_get_iva_device); struct device *omap2_get_l3_device(void) { WARN_ON_ONCE(!l3_dev); return l3_dev; } +EXPORT_SYMBOL(omap2_get_l3_device); struct device *omap4_get_dsp_device(void) { @@ -106,8 +109,9 @@ static void omap2_init_processor_devices(void) int omap_set_pwrdm_state(struct powerdomain *pwrdm, u32 state) { u32 cur_state; - int sleep_switch = 0; + int sleep_switch = -1; int ret = 0; + int hwsup = 0; if (pwrdm == NULL || IS_ERR(pwrdm)) return -EINVAL; @@ -127,6 +131,7 @@ int omap_set_pwrdm_state(struct powerdomain *pwrdm, u32 state) (pwrdm->flags & PWRDM_HAS_LOWPOWERSTATECHANGE)) { sleep_switch = LOWPOWERSTATE_SWITCH; } else { + hwsup = clkdm_is_idle(pwrdm->pwrdm_clkdms[0]); clkdm_wakeup(pwrdm->pwrdm_clkdms[0]); pwrdm_wait_transition(pwrdm); sleep_switch = FORCEWAKEUP_SWITCH; @@ -142,7 +147,7 @@ int omap_set_pwrdm_state(struct powerdomain *pwrdm, u32 state) switch (sleep_switch) { case FORCEWAKEUP_SWITCH: - if (pwrdm->pwrdm_clkdms[0]->flags & CLKDM_CAN_ENABLE_AUTO) + if (hwsup) clkdm_allow_idle(pwrdm->pwrdm_clkdms[0]); else clkdm_sleep(pwrdm->pwrdm_clkdms[0]); @@ -181,7 +186,7 @@ static int __init omap2_set_init_voltage(char *vdd_name, char *clk_name, goto exit; } - voltdm = omap_voltage_domain_lookup(vdd_name); + voltdm = voltdm_lookup(vdd_name); if (IS_ERR(voltdm)) { printk(KERN_ERR "%s: Unable to get vdd pointer for vdd_%s\n", __func__, vdd_name); @@ -196,15 +201,26 @@ static int __init omap2_set_init_voltage(char *vdd_name, char *clk_name, } freq = clk->rate; - clk_put(clk); opp = opp_find_freq_ceil(dev, &freq); if (IS_ERR(opp)) { - printk(KERN_ERR "%s: unable to find boot up OPP for vdd_%s\n", - __func__, vdd_name); - goto exit; + printk(KERN_ERR "%s: unable to find boot OPP f=%ld for vdd%s\n", + __func__, freq, vdd_name); + opp = opp_find_freq_floor(dev, &freq); + if (IS_ERR(opp)) { + pr_err("%s: unable to find a lower to %ld either!\n", + __func__, freq); + goto exit_ck; + } + + if (clk_set_rate(clk, freq) < 0) { + pr_err("%s: unable to set the clock to %ld either!\n", + __func__, freq); + goto exit_ck; + } } + clk_put(clk); bootup_volt = opp_get_voltage(opp); if (!bootup_volt) { printk(KERN_ERR "%s: unable to find voltage corresponding" @@ -212,9 +228,11 @@ static int __init omap2_set_init_voltage(char *vdd_name, char *clk_name, goto exit; } - omap_voltage_scale_vdd(voltdm, bootup_volt); + voltdm_scale(voltdm, bootup_volt); return 0; +exit_ck: + clk_put(clk); exit: printk(KERN_ERR "%s: Unable to put vdd_%s to its init voltage\n\n", __func__, vdd_name); @@ -226,7 +244,7 @@ static void __init omap3_init_voltages(void) if (!cpu_is_omap34xx()) return; - omap2_set_init_voltage("mpu", "dpll1_ck", mpu_dev); + omap2_set_init_voltage("mpu_iva", "dpll1_ck", mpu_dev); omap2_set_init_voltage("core", "l3_ick", l3_dev); } @@ -235,7 +253,10 @@ static void __init omap4_init_voltages(void) if (!cpu_is_omap44xx()) return; - omap2_set_init_voltage("mpu", "dpll_mpu_ck", mpu_dev); + if (cpu_is_omap446x()) + omap2_set_init_voltage("mpu", "virt_dpll_mpu_ck", mpu_dev); + else + omap2_set_init_voltage("mpu", "dpll_mpu_ck", mpu_dev); omap2_set_init_voltage("core", "l3_div_ck", l3_dev); omap2_set_init_voltage("iva", "dpll_iva_m5x2_ck", iva_dev); } @@ -251,9 +272,8 @@ postcore_initcall(omap2_common_pm_init); static int __init omap2_common_pm_late_init(void) { - /* Init the OMAP TWL parameters */ - omap3_twl_init(); - omap4_twl_init(); + /* Init the OMAP PMIC parameters */ + omap_pmic_init(); /* Init the voltage layer */ omap_voltage_late_init(); diff --git a/arch/arm/mach-omap2/pm.h b/arch/arm/mach-omap2/pm.h index 797bfd12b643..8d9e86a318bd 100644 --- a/arch/arm/mach-omap2/pm.h +++ b/arch/arm/mach-omap2/pm.h @@ -19,8 +19,12 @@ extern void *omap3_secure_ram_storage; extern void omap3_pm_off_mode_enable(int); extern void omap_sram_idle(void); extern int omap3_can_sleep(void); +extern int omap4_can_sleep(void); extern int omap_set_pwrdm_state(struct powerdomain *pwrdm, u32 state); extern int omap3_idle_init(void); +extern int omap4_idle_init(void); +extern void omap4_enter_sleep(unsigned int cpu, unsigned int power_state); +extern void omap4_trigger_ioctrl(void); #if defined(CONFIG_PM_OPP) extern int omap3_opp_init(void); @@ -36,11 +40,16 @@ static inline int omap4_opp_init(void) } #endif +/* + * cpuidle mach specific parameters + * + * The board code can override the default C-states definition using + * omap3_pm_init_cpuidle + */ struct cpuidle_params { - u8 valid; - u32 sleep_latency; - u32 wake_latency; - u32 threshold; + u32 exit_latency; /* exit_latency = sleep + wake-up latencies */ + u32 target_residency; + u8 valid; /* validates the C-state */ }; #if defined(CONFIG_PM) && defined(CONFIG_CPU_IDLE) @@ -73,10 +82,6 @@ extern u32 sleep_while_idle; #define sleep_while_idle 0 #endif -#if defined(CONFIG_CPU_IDLE) -extern void omap3_cpuidle_update_states(u32, u32); -#endif - #if defined(CONFIG_PM_DEBUG) && defined(CONFIG_DEBUG_FS) extern void pm_dbg_update_time(struct powerdomain *pwrdm, int prev); extern int pm_dbg_regset_save(int reg_set); @@ -124,16 +129,56 @@ static inline int omap_devinit_smartreflex(void) static inline void omap_enable_smartreflex_on_init(void) {} #endif +/** + * struct omap_pmic_map - Describe the OMAP PMIC data for OMAP + * @name: name of the voltage domain + * @pmic_data: pmic data associated with it + * @omap_chip: initialize with OMAP_CHIP_INIT the OMAP chips this data maps to + * @special_action: callback for any specific action to take for that map + * + * Since we support multiple PMICs each potentially functioning on multiple + * OMAP devices, we describe the parameters in a map allowing us to reuse the + * data as necessary. + */ +struct omap_pmic_map { + char *name; + struct omap_voltdm_pmic *pmic_data; + struct omap_chip_id omap_chip; + int (*special_action)(struct voltagedomain *); +}; + +#ifdef CONFIG_PM +extern int omap_pmic_register_data(struct omap_pmic_map *map); +#else +static inline int omap_pmic_register_data(struct omap_pmic_map *map) +{ + return -EINVAL; +} +#endif +extern void omap_pmic_init(void); + #ifdef CONFIG_TWL4030_CORE -extern int omap3_twl_init(void); -extern int omap4_twl_init(void); +extern int omap_twl_init(void); extern int omap3_twl_set_sr_bit(bool enable); #else -static inline int omap3_twl_init(void) +static inline int omap_twl_init(void) +{ + return -EINVAL; +} +#endif + +#ifdef CONFIG_OMAP_TPS6236X +extern int omap_tps6236x_board_setup(bool use_62361, int gpio_vsel0, + int gpio_vsel1, int pull0, int pull1); +extern int omap_tps6236x_init(void); + +#else +static inline int omap_tps6236x_board_setup(bool use_62361, int gpio_vsel0, + int gpio_vsel1, int pull0, int pull1) { return -EINVAL; } -static inline int omap4_twl_init(void) +static inline int omap_tps6236x_init(void) { return -EINVAL; } diff --git a/arch/arm/mach-omap2/pm34xx.c b/arch/arm/mach-omap2/pm34xx.c index 0c5e3a46a3ad..c155c9d1c82c 100644 --- a/arch/arm/mach-omap2/pm34xx.c +++ b/arch/arm/mach-omap2/pm34xx.c @@ -779,18 +779,6 @@ void omap3_pm_off_mode_enable(int enable) else state = PWRDM_POWER_RET; -#ifdef CONFIG_CPU_IDLE - /* - * Erratum i583: implementation for ES rev < Es1.2 on 3630. We cannot - * enable OFF mode in a stable form for previous revisions, restrict - * instead to RET - */ - if (IS_PM34XX_ERRATUM(PM_SDRC_WAKEUP_ERRATUM_i583)) - omap3_cpuidle_update_states(state, PWRDM_POWER_RET); - else - omap3_cpuidle_update_states(state, state); -#endif - list_for_each_entry(pwrst, &pwrst_list, node) { if (IS_PM34XX_ERRATUM(PM_SDRC_WAKEUP_ERRATUM_i583) && pwrst->pwrdm == core_pwrdm && @@ -895,8 +883,6 @@ static int __init omap3_pm_init(void) pm_errata_configure(); - printk(KERN_ERR "Power Management for TI OMAP3.\n"); - /* XXX prcm_setup_regs needs to be before enabling hw * supervised mode for powerdomains */ prcm_setup_regs(); diff --git a/arch/arm/mach-omap2/pm44xx.c b/arch/arm/mach-omap2/pm44xx.c index 76cfff2db514..5a7c8dbb33e7 100644 --- a/arch/arm/mach-omap2/pm44xx.c +++ b/arch/arm/mach-omap2/pm44xx.c @@ -1,8 +1,9 @@ /* * OMAP4 Power Management Routines * - * Copyright (C) 2010 Texas Instruments, Inc. + * Copyright (C) 2010-2011 Texas Instruments, Inc. * Rajendra Nayak <rnayak@ti.com> + * Santosh Shilimkar <santosh.shilimkar@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 @@ -12,12 +13,31 @@ #include <linux/pm.h> #include <linux/suspend.h> #include <linux/module.h> +#include <linux/clk.h> #include <linux/list.h> #include <linux/err.h> #include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/delay.h> -#include "powerdomain.h" #include <mach/omap4-common.h> +#include <plat/common.h> + +#include "powerdomain.h" +#include "clockdomain.h" +#include "pm.h" +#include "prm-regbits-44xx.h" +#include "prcm44xx.h" +#include "prm44xx.h" +#include "prminst44xx.h" + +#include "clock.h" +#include "cm2_44xx.h" +#include "cm1_44xx.h" +#include "cm-regbits-44xx.h" +#include "cminst44xx.h" + struct power_state { struct powerdomain *pwrdm; @@ -29,11 +49,129 @@ struct power_state { }; static LIST_HEAD(pwrst_list); +static struct powerdomain *mpu_pwrdm, *cpu0_pwrdm; +static struct powerdomain *core_pwrdm, *per_pwrdm; + +#define MAX_IOPAD_LATCH_TIME 1000 +void omap4_trigger_ioctrl(void) +{ + int i = 0; + + /* Trigger WUCLKIN enable */ + omap4_prminst_rmw_inst_reg_bits(OMAP4430_WUCLK_CTRL_MASK, OMAP4430_WUCLK_CTRL_MASK, + OMAP4430_PRM_PARTITION, OMAP4430_PRM_DEVICE_INST, OMAP4_PRM_IO_PMCTRL_OFFSET); + omap_test_timeout( + ((omap4_prminst_read_inst_reg(OMAP4430_PRM_PARTITION, OMAP4430_PRM_DEVICE_INST, OMAP4_PRM_IO_PMCTRL_OFFSET) + >> OMAP4430_WUCLK_STATUS_SHIFT) == 1), + MAX_IOPAD_LATCH_TIME, i); + /* Trigger WUCLKIN disable */ + omap4_prminst_rmw_inst_reg_bits(OMAP4430_WUCLK_CTRL_MASK, 0x0, + OMAP4430_PRM_PARTITION, OMAP4430_PRM_DEVICE_INST, OMAP4_PRM_IO_PMCTRL_OFFSET); + return; +} #ifdef CONFIG_SUSPEND +/* This is a common low power function called from suspend and + * cpuidle + */ +void omap4_enter_sleep(unsigned int cpu, unsigned int power_state) +{ + int cpu0_next_state = PWRDM_POWER_ON; + int per_next_state = PWRDM_POWER_ON; + int core_next_state = PWRDM_POWER_ON; + + pwrdm_clear_all_prev_pwrst(cpu0_pwrdm); + pwrdm_clear_all_prev_pwrst(mpu_pwrdm); + pwrdm_clear_all_prev_pwrst(core_pwrdm); + pwrdm_clear_all_prev_pwrst(per_pwrdm); + + cpu0_next_state = pwrdm_read_next_pwrst(cpu0_pwrdm); + per_next_state = pwrdm_read_next_pwrst(per_pwrdm); + core_next_state = pwrdm_read_next_pwrst(core_pwrdm); + + if (core_next_state < PWRDM_POWER_ON) { + omap_uart_prepare_idle(0); + omap_uart_prepare_idle(1); + omap_uart_prepare_idle(2); + omap_uart_prepare_idle(3); + omap2_gpio_prepare_for_idle(0); + omap4_trigger_ioctrl(); + } + + omap4_enter_lowpower(cpu, power_state); + + if (core_next_state < PWRDM_POWER_ON) { + omap2_gpio_resume_after_idle(); + omap_uart_resume_idle(0); + omap_uart_resume_idle(1); + omap_uart_resume_idle(2); + omap_uart_resume_idle(3); + } + + return; +} + static int omap4_pm_suspend(void) { - do_wfi(); + struct power_state *pwrst; + int state, ret = 0; + + /* Wakeup timer from suspend */ + if (wakeup_timer_seconds || wakeup_timer_milliseconds) + omap2_pm_wakeup_on_timer(wakeup_timer_seconds, + wakeup_timer_milliseconds); + + /* Save current powerdomain state */ + list_for_each_entry(pwrst, &pwrst_list, node) { + pwrst->saved_state = pwrdm_read_next_pwrst(pwrst->pwrdm); + } + + /* Set targeted power domain states by suspend */ + list_for_each_entry(pwrst, &pwrst_list, node) { +#ifdef CONFIG_OMAP_ALLOW_OSWR + if ((!strcmp(pwrst->pwrdm->name, "cpu0_pwrdm")) || + (!strcmp(pwrst->pwrdm->name, "cpu1_pwrdm"))) + continue; + + /*OSWR is supported on silicon > ES2.0 */ + if ((pwrst->pwrdm->pwrsts_logic_ret == PWRSTS_OFF_RET) + && (omap_rev() >= OMAP4430_REV_ES2_1)) + pwrdm_set_logic_retst(pwrst->pwrdm, + PWRDM_POWER_OFF); +#endif + pwrst->next_state = PWRDM_POWER_RET; + omap_set_pwrdm_state(pwrst->pwrdm, pwrst->next_state); + } + + /* + * For MPUSS to hit power domain retention(CSWR or OSWR), + * CPU0 and CPU1 power domain needs to be in OFF or DORMANT + * state. For MPUSS to reach off-mode. CPU0 and CPU1 power domain + * should be in off state. + * Only master CPU followes suspend path. All other CPUs follow + * cpu-hotplug path in system wide suspend. On OMAP4, CPU power + * domain CSWR is not supported by hardware. + * More details can be found in OMAP4430 TRM section 4.3.4.2. + */ + omap_uart_prepare_suspend(); + omap4_enter_sleep(0, PWRDM_POWER_OFF); + + /* Restore next powerdomain state */ + list_for_each_entry(pwrst, &pwrst_list, node) { + state = pwrdm_read_prev_pwrst(pwrst->pwrdm); + if (state > pwrst->next_state) { + pr_info("Powerdomain (%s) didn't enter " + "target state %d\n", + pwrst->pwrdm->name, pwrst->next_state); + ret = -1; + } + omap_set_pwrdm_state(pwrst->pwrdm, pwrst->saved_state); + } + if (ret) + pr_err("Could not enter target state in pm_suspend\n"); + else + pr_err("Successfully put all powerdomains to target state\n"); + return 0; } @@ -71,8 +209,26 @@ static const struct platform_suspend_ops omap_pm_ops = { .enter = omap4_pm_enter, .valid = suspend_valid_only_mem, }; +#else +void omap4_enter_sleep(unsigned int cpu, unsigned int power_state){ return; } #endif /* CONFIG_SUSPEND */ +/* + * Enable hardware supervised mode for all clockdomains if it's + * supported. Initiate sleep transition for other clockdomains, if + * they are not used + */ +static int __init clkdms_setup(struct clockdomain *clkdm, void *unused) +{ + if (clkdm->flags & CLKDM_CAN_ENABLE_AUTO) + clkdm_allow_idle(clkdm); + else if (clkdm->flags & CLKDM_CAN_FORCE_SLEEP && + atomic_read(&clkdm->usecount) == 0) + clkdm_sleep(clkdm); + return 0; +} + + static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused) { struct power_state *pwrst; @@ -83,11 +239,150 @@ static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused) pwrst = kmalloc(sizeof(struct power_state), GFP_ATOMIC); if (!pwrst) return -ENOMEM; + pwrst->pwrdm = pwrdm; - pwrst->next_state = PWRDM_POWER_ON; + if ((!strcmp(pwrdm->name, "mpu_pwrdm")) || + (!strcmp(pwrdm->name, "core_pwrdm")) || + (!strcmp(pwrdm->name, "cpu0_pwrdm")) || + (!strcmp(pwrdm->name, "cpu1_pwrdm"))) + pwrst->next_state = PWRDM_POWER_ON; + else + pwrst->next_state = PWRDM_POWER_RET; list_add(&pwrst->node, &pwrst_list); - return pwrdm_set_next_pwrst(pwrst->pwrdm, pwrst->next_state); + return omap_set_pwrdm_state(pwrst->pwrdm, pwrst->next_state); +} + +static void __init prcm_setup_regs(void) +{ + struct clk *dpll_abe_ck, *dpll_core_ck, *dpll_iva_ck; + struct clk *dpll_mpu_ck, *dpll_per_ck, *dpll_usb_ck; + struct clk *dpll_unipro_ck; + /*Enable all the DPLL autoidle */ + dpll_abe_ck = clk_get(NULL, "dpll_abe_ck"); + omap3_dpll_allow_idle(dpll_abe_ck); + dpll_core_ck = clk_get(NULL, "dpll_core_ck"); + omap3_dpll_allow_idle(dpll_core_ck); + dpll_iva_ck = clk_get(NULL, "dpll_iva_ck"); + omap3_dpll_allow_idle(dpll_iva_ck); + dpll_mpu_ck = clk_get(NULL, "dpll_mpu_ck"); + omap3_dpll_allow_idle(dpll_mpu_ck); + dpll_per_ck = clk_get(NULL, "dpll_per_ck"); + omap3_dpll_allow_idle(dpll_per_ck); + dpll_usb_ck = clk_get(NULL, "dpll_usb_ck"); + omap3_dpll_allow_idle(dpll_usb_ck); + dpll_unipro_ck = clk_get(NULL, "dpll_unipro_ck"); + omap3_dpll_allow_idle(dpll_unipro_ck); + /* Enable autogating for all DPLL post dividers */ + omap4_cminst_rmw_inst_reg_bits(OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK, 0x0, + OMAP4430_CM1_PARTITION, OMAP4430_CM1_CKGEN_INST, OMAP4_CM_DIV_M2_DPLL_MPU_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_HSDIVIDER_CLKOUT1_GATE_CTRL_MASK, 0x0, + OMAP4430_CM1_PARTITION, OMAP4430_CM1_CKGEN_INST, OMAP4_CM_DIV_M4_DPLL_IVA_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_HSDIVIDER_CLKOUT2_GATE_CTRL_MASK, 0x0, + OMAP4430_CM1_PARTITION, OMAP4430_CM1_CKGEN_INST, OMAP4_CM_DIV_M5_DPLL_IVA_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK, 0x0, + OMAP4430_CM1_PARTITION, OMAP4430_CM1_CKGEN_INST, OMAP4_CM_DIV_M2_DPLL_CORE_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_DPLL_CLKOUTHIF_GATE_CTRL_MASK, 0x0, + OMAP4430_CM1_PARTITION, OMAP4430_CM1_CKGEN_INST, OMAP4_CM_DIV_M3_DPLL_CORE_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_HSDIVIDER_CLKOUT1_GATE_CTRL_MASK, 0x0, + OMAP4430_CM1_PARTITION, OMAP4430_CM1_CKGEN_INST, OMAP4_CM_DIV_M4_DPLL_CORE_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_HSDIVIDER_CLKOUT2_GATE_CTRL_MASK, 0x0, + OMAP4430_CM1_PARTITION, OMAP4430_CM1_CKGEN_INST, OMAP4_CM_DIV_M5_DPLL_CORE_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_HSDIVIDER_CLKOUT3_GATE_CTRL_MASK, 0x0, + OMAP4430_CM1_PARTITION, OMAP4430_CM1_CKGEN_INST, OMAP4_CM_DIV_M6_DPLL_CORE_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_HSDIVIDER_CLKOUT4_GATE_CTRL_MASK, 0x0, + OMAP4430_CM1_PARTITION, OMAP4430_CM1_CKGEN_INST, OMAP4_CM_DIV_M7_DPLL_CORE_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK, 0x0, + OMAP4430_CM2_PARTITION, OMAP4430_CM2_CKGEN_INST, OMAP4_CM_DIV_M2_DPLL_PER_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_DPLL_CLKOUTX2_GATE_CTRL_MASK, 0x0, + OMAP4430_CM2_PARTITION, OMAP4430_CM2_CKGEN_INST, OMAP4_CM_DIV_M2_DPLL_PER_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_DPLL_CLKOUTHIF_GATE_CTRL_MASK, 0x0, + OMAP4430_CM2_PARTITION, OMAP4430_CM2_CKGEN_INST, OMAP4_CM_DIV_M3_DPLL_PER_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_HSDIVIDER_CLKOUT1_GATE_CTRL_MASK, 0x0, + OMAP4430_CM2_PARTITION, OMAP4430_CM2_CKGEN_INST, OMAP4_CM_DIV_M4_DPLL_PER_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_HSDIVIDER_CLKOUT2_GATE_CTRL_MASK, 0x0, + OMAP4430_CM2_PARTITION, OMAP4430_CM2_CKGEN_INST, OMAP4_CM_DIV_M5_DPLL_PER_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_HSDIVIDER_CLKOUT3_GATE_CTRL_MASK, 0x0, + OMAP4430_CM2_PARTITION, OMAP4430_CM2_CKGEN_INST, OMAP4_CM_DIV_M6_DPLL_PER_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_HSDIVIDER_CLKOUT4_GATE_CTRL_MASK, 0x0, + OMAP4430_CM2_PARTITION, OMAP4430_CM2_CKGEN_INST, OMAP4_CM_DIV_M7_DPLL_PER_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK, 0x0, + OMAP4430_CM1_PARTITION, OMAP4430_CM1_CKGEN_INST, OMAP4_CM_DIV_M2_DPLL_ABE_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_DPLL_CLKOUTX2_GATE_CTRL_MASK, 0x0, + OMAP4430_CM1_PARTITION, OMAP4430_CM1_CKGEN_INST, OMAP4_CM_DIV_M2_DPLL_ABE_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_DPLL_CLKOUTHIF_GATE_CTRL_MASK, 0x0, + OMAP4430_CM1_PARTITION, OMAP4430_CM1_CKGEN_INST, OMAP4_CM_DIV_M3_DPLL_ABE_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_DPLL_CLKOUT_GATE_CTRL_MASK, 0x0, + OMAP4430_CM2_PARTITION, OMAP4430_CM2_CKGEN_INST, OMAP4_CM_DIV_M2_DPLL_USB_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_DPLL_CLKDCOLDO_GATE_CTRL_MASK, 0x0, + OMAP4430_CM2_PARTITION, OMAP4430_CM2_CKGEN_INST, OMAP4_CM_CLKDCOLDO_DPLL_USB_OFFSET); + omap4_cminst_rmw_inst_reg_bits(OMAP4430_DPLL_CLKOUTX2_GATE_CTRL_MASK, 0x0, + OMAP4430_CM2_PARTITION, OMAP4430_CM2_CKGEN_INST, OMAP4_CM_DIV_M2_DPLL_UNIPRO_OFFSET); + /* Enable IO_ST interrupt */ + omap4_prminst_rmw_inst_reg_bits(OMAP4430_IO_ST_MASK, OMAP4430_IO_ST_MASK, + OMAP4430_PRM_PARTITION, OMAP4430_PRM_OCP_SOCKET_INST, OMAP4_PRM_IRQENABLE_MPU_OFFSET); + + /* Enable GLOBAL_WUEN */ + omap4_prminst_rmw_inst_reg_bits(OMAP4430_GLOBAL_WUEN_MASK, OMAP4430_GLOBAL_WUEN_MASK, + OMAP4430_PRM_PARTITION, OMAP4430_PRM_DEVICE_INST, OMAP4_PRM_IO_PMCTRL_OFFSET); + /* + * Errata ID: i608 Impacted OMAP4430 ES 1.0,2.0,2.1,2.2 + * On OMAP4, Retention-Till-Access Memory feature is not working + * reliably and hardware recommondation is keep it disabled by + * default + */ + omap4_prminst_rmw_inst_reg_bits(OMAP4430_DISABLE_RTA_EXPORT_MASK, + 0x1 << OMAP4430_DISABLE_RTA_EXPORT_SHIFT, + OMAP4430_PRM_PARTITION, OMAP4430_PRM_DEVICE_INST, OMAP4_PRM_SRAM_WKUP_SETUP_OFFSET); + omap4_prminst_rmw_inst_reg_bits(OMAP4430_DISABLE_RTA_EXPORT_MASK, + 0x1 << OMAP4430_DISABLE_RTA_EXPORT_SHIFT, + OMAP4430_PRM_PARTITION, OMAP4430_PRM_DEVICE_INST, OMAP4_PRM_LDO_SRAM_CORE_SETUP_OFFSET); + omap4_prminst_rmw_inst_reg_bits(OMAP4430_DISABLE_RTA_EXPORT_MASK, + 0x1 << OMAP4430_DISABLE_RTA_EXPORT_SHIFT, + OMAP4430_PRM_PARTITION, OMAP4430_PRM_DEVICE_INST, OMAP4_PRM_LDO_SRAM_MPU_SETUP_OFFSET); + omap4_prminst_rmw_inst_reg_bits(OMAP4430_DISABLE_RTA_EXPORT_MASK, + 0x1 << OMAP4430_DISABLE_RTA_EXPORT_SHIFT, + OMAP4430_PRM_PARTITION, OMAP4430_PRM_DEVICE_INST, OMAP4_PRM_LDO_SRAM_IVA_SETUP_OFFSET); + /* Toggle CLKREQ in RET and OFF states */ + omap4_prminst_write_inst_reg(0x2, OMAP4430_PRM_PARTITION, + OMAP4430_PRM_DEVICE_INST, OMAP4_PRM_CLKREQCTRL_OFFSET); + /* + * De-assert PWRREQ signal in Device OFF state + * 0x3: PWRREQ is de-asserted if all voltage domain are in + * OFF state. Conversely, PWRREQ is asserted upon any + * voltage domain entering or staying in ON or SLEEP or + * RET state. + */ + omap4_prminst_write_inst_reg(0x3, OMAP4430_PRM_PARTITION, + OMAP4430_PRM_DEVICE_INST, OMAP4_PRM_PWRREQCTRL_OFFSET); +} +static irqreturn_t prcm_interrupt_handler (int irq, void *dev_id) +{ + u32 irqenable_mpu, irqstatus_mpu; + + irqenable_mpu = omap4_prm_read_inst_reg(OMAP4430_PRM_OCP_SOCKET_INST, + OMAP4_PRM_IRQENABLE_MPU_OFFSET); + irqstatus_mpu = omap4_prm_read_inst_reg(OMAP4430_PRM_OCP_SOCKET_INST, + OMAP4_PRM_IRQSTATUS_MPU_OFFSET); + + /* Check if a IO_ST interrupt */ + if (irqstatus_mpu & OMAP4430_IO_ST_MASK) { + omap4_trigger_ioctrl(); + } + + /* Clear the interrupt */ + irqstatus_mpu &= irqenable_mpu; + omap4_prm_write_inst_reg(irqstatus_mpu, OMAP4430_PRM_OCP_SOCKET_INST, + OMAP4_PRM_IRQSTATUS_MPU_OFFSET); + + return IRQ_HANDLED; +} + +int omap4_can_sleep(void) +{ + if (!omap_uart_can_sleep()) + return -1; + return 0; } /** @@ -99,24 +394,85 @@ static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused) static int __init omap4_pm_init(void) { int ret; + struct clockdomain *emif_clkdm, *mpuss_clkdm, *l3_1_clkdm; + struct clockdomain *ducati_clkdm, *l3_2_clkdm; if (!cpu_is_omap44xx()) return -ENODEV; + if (omap_rev() == OMAP4430_REV_ES1_0) { + WARN(1, "Power Management not supported on OMAP4430 ES1.0\n"); + return -ENODEV; + } + pr_err("Power Management for TI OMAP4.\n"); -#ifdef CONFIG_PM + prcm_setup_regs(); + + ret = request_irq(OMAP44XX_IRQ_PRCM, + (irq_handler_t)prcm_interrupt_handler, + IRQF_DISABLED, "prcm", NULL); + if (ret) { + printk(KERN_ERR "request_irq failed to register for 0x%x\n", + OMAP44XX_IRQ_PRCM); + goto err2; + } + + local_irq_disable(); + ret = pwrdm_for_each(pwrdms_setup, NULL); if (ret) { pr_err("Failed to setup powerdomains\n"); goto err2; } -#endif + + /* + * The dynamic dependency between MPUSS -> MEMIF and + * MPUSS -> L3_* and DUCATI -> doesn't work as expected. + * The hardware recommendation is to keep above dependencies. + * Without this system locks up or randomly crashesh. + */ + mpuss_clkdm = clkdm_lookup("mpuss_clkdm"); + emif_clkdm = clkdm_lookup("l3_emif_clkdm"); + l3_1_clkdm = clkdm_lookup("l3_1_clkdm"); + l3_2_clkdm = clkdm_lookup("l3_2_clkdm"); + ducati_clkdm = clkdm_lookup("ducati_clkdm"); + if ((!mpuss_clkdm) || (!emif_clkdm) || (!l3_1_clkdm) || + (!l3_2_clkdm) || (!ducati_clkdm)) + goto err2; + + ret = clkdm_add_wkdep(mpuss_clkdm, emif_clkdm); + ret |= clkdm_add_wkdep(mpuss_clkdm, l3_1_clkdm); + ret |= clkdm_add_wkdep(mpuss_clkdm, l3_2_clkdm); + ret |= clkdm_add_wkdep(ducati_clkdm, l3_1_clkdm); + ret |= clkdm_add_wkdep(ducati_clkdm, l3_2_clkdm); + if (ret) { + pr_err("Failed to add MPUSS -> L3/EMIF, DUCATI -> L3" + "wakeup dependency\n"); + goto err2; + } + + (void) clkdm_for_each(clkdms_setup, NULL); + + ret = omap4_mpuss_init(); + if (ret) { + pr_err("Failed to initialise OMAP4 MPUSS\n"); + goto err2; + } #ifdef CONFIG_SUSPEND suspend_set_ops(&omap_pm_ops); #endif /* CONFIG_SUSPEND */ + mpu_pwrdm = pwrdm_lookup("mpu_pwrdm"); + cpu0_pwrdm = pwrdm_lookup("cpu0_pwrdm"); + core_pwrdm = pwrdm_lookup("core_pwrdm"); + per_pwrdm = pwrdm_lookup("l4per_pwrdm"); + + omap4_idle_init(); + + local_irq_enable(); + err2: return ret; } diff --git a/arch/arm/mach-omap2/powerdomain.c b/arch/arm/mach-omap2/powerdomain.c index 9af08473bf10..4e4ea7be54d0 100644 --- a/arch/arm/mach-omap2/powerdomain.c +++ b/arch/arm/mach-omap2/powerdomain.c @@ -19,6 +19,8 @@ #include <linux/list.h> #include <linux/errno.h> #include <linux/string.h> +#include <linux/slab.h> + #include <trace/events/power.h> #include "cm2xxx_3xxx.h" @@ -77,6 +79,7 @@ static struct powerdomain *_pwrdm_lookup(const char *name) static int _pwrdm_register(struct powerdomain *pwrdm) { int i; + struct voltagedomain *voltdm; if (!pwrdm || !pwrdm->name) return -EINVAL; @@ -94,6 +97,16 @@ static int _pwrdm_register(struct powerdomain *pwrdm) if (_pwrdm_lookup(pwrdm->name)) return -EEXIST; + voltdm = voltdm_lookup(pwrdm->voltdm.name); + if (!voltdm) { + pr_err("powerdomain: %s: voltagedomain %s does not exist\n", + pwrdm->name, pwrdm->voltdm.name); + return -EINVAL; + } + pwrdm->voltdm.ptr = voltdm; + INIT_LIST_HEAD(&pwrdm->voltdm_node); + voltdm_add_pwrdm(voltdm, pwrdm); + list_add(&pwrdm->node, &pwrdm_list); /* Initialize the powerdomain's state counter */ @@ -108,6 +121,13 @@ static int _pwrdm_register(struct powerdomain *pwrdm) pwrdm->state = pwrdm_read_pwrst(pwrdm); pwrdm->state_counter[pwrdm->state] = 1; + /* Initialize priority ordered list for wakeup latency constraint */ + spin_lock_init(&pwrdm->wakeuplat_lock); + plist_head_init(&pwrdm->wakeuplat_dev_list, &pwrdm->wakeuplat_lock); + + /* res_mutex protects res_list add and del ops */ + mutex_init(&pwrdm->wakeuplat_mutex); + pr_debug("powerdomain: registered %s\n", pwrdm->name); return 0; @@ -208,6 +228,7 @@ void pwrdm_init(struct powerdomain **pwrdm_list, struct pwrdm_ops *custom_funcs) { struct powerdomain **p = NULL; + if (!custom_funcs) WARN(1, "powerdomain: No custom pwrdm functions registered\n"); else @@ -383,6 +404,18 @@ int pwrdm_for_each_clkdm(struct powerdomain *pwrdm, } /** + * pwrdm_get_voltdm - return a ptr to the voltdm that this pwrdm resides in + * @pwrdm: struct powerdomain * + * + * Return a pointer to the struct voltageomain that the specified powerdomain + * @pwrdm exists in. + */ +struct voltagedomain *pwrdm_get_voltdm(struct powerdomain *pwrdm) +{ + return pwrdm->voltdm.ptr; +} + +/** * pwrdm_get_mem_bank_count - get number of memory banks in this powerdomain * @pwrdm: struct powerdomain * * @@ -999,3 +1032,163 @@ bool pwrdm_can_ever_lose_context(struct powerdomain *pwrdm) return 0; } + + +/** + * pwrdm_wakeuplat_set_constraint - Set powerdomain wakeup latency constraint + * @pwrdm: struct powerdomain * to which requesting device belongs to + * @dev: struct device * of requesting device + * @t: wakeup latency constraint in microseconds + * + * Adds new entry to powerdomain's wakeup latency constraint list. + * If the requesting device already exists in the list, old value is + * overwritten. Checks whether current power state is still adequate. + * Returns -EINVAL if the powerdomain or device pointer is NULL, + * or -ENOMEM if kmalloc fails, or -ERANGE if constraint can't be met, + * or returns 0 upon success. + */ +int pwrdm_wakeuplat_set_constraint (struct powerdomain *pwrdm, + struct device *dev, unsigned long t) +{ + struct wakeuplat_dev_list *user; + int found = 0, ret = 0; + + if (!pwrdm || !dev) { + WARN(1, "OMAP PM: %s: invalid parameter(s)", __func__); + return -EINVAL; + } + + mutex_lock(&pwrdm->wakeuplat_mutex); + + plist_for_each_entry(user, &pwrdm->wakeuplat_dev_list, node) { + if (user->dev == dev) { + found = 1; + break; + } + } + + /* Add new entry to the list or update existing request */ + if (found && user->constraint_us == t) { + goto exit_set; + } else if (!found) { + user = kzalloc(sizeof(struct wakeuplat_dev_list), GFP_KERNEL); + if (!user) { + pr_err("OMAP PM: FATAL ERROR: kzalloc failed\n"); + ret = -ENOMEM; + goto exit_set; + } + user->dev = dev; + } else { + plist_del(&user->node, &pwrdm->wakeuplat_dev_list); + } + + plist_node_init(&user->node, t); + plist_add(&user->node, &pwrdm->wakeuplat_dev_list); + user->node.prio = user->constraint_us = t; + + ret = pwrdm_wakeuplat_update_pwrst(pwrdm); + +exit_set: + mutex_unlock(&pwrdm->wakeuplat_mutex); + + return ret; +} + +/** + * pwrdm_wakeuplat_release_constraint - Release powerdomain wkuplat constraint + * @pwrdm: struct powerdomain * to which requesting device belongs to + * @dev: struct device * of requesting device + * + * Removes device's entry from powerdomain's wakeup latency constraint list. + * Checks whether current power state is still adequate. + * Returns -EINVAL if the powerdomain or device pointer is NULL or + * no such entry exists in the list, or returns 0 upon success. + */ +int pwrdm_wakeuplat_release_constraint (struct powerdomain *pwrdm, + struct device *dev) +{ + struct wakeuplat_dev_list *user; + int found = 0, ret = 0; + + if (!pwrdm || !dev) { + WARN(1, "OMAP PM: %s: invalid parameter(s)", __func__); + return -EINVAL; + } + + mutex_lock(&pwrdm->wakeuplat_mutex); + + plist_for_each_entry(user, &pwrdm->wakeuplat_dev_list, node) { + if (user->dev == dev) { + found = 1; + break; + } + } + + if (!found) { + pr_err("OMAP PM: Error: no prior constraint to release\n"); + ret = -EINVAL; + goto exit_rls; + } + + plist_del(&user->node, &pwrdm->wakeuplat_dev_list); + kfree(user); + + ret = pwrdm_wakeuplat_update_pwrst(pwrdm); + +exit_rls: + mutex_unlock(&pwrdm->wakeuplat_mutex); + + return ret; +} + +/** + * pwrdm_wakeuplat_update_pwrst - Update power domain power state if needed + * @pwrdm: struct powerdomain * to which requesting device belongs to + * + * Finds minimum latency value from all entries in the list and + * the power domain power state neeting the constraint. Programs + * new state if it is different from current power state. + * Returns -EINVAL if the powerdomain or device pointer is NULL or + * no such entry exists in the list, or -ERANGE if constraint can't be met, + * or returns 0 upon success. + */ +int pwrdm_wakeuplat_update_pwrst(struct powerdomain *pwrdm) +{ + struct plist_node *node; + int ret = 0, new_state; + unsigned long min_latency = -1; + + if (!plist_head_empty(&pwrdm->wakeuplat_dev_list)) { + node = plist_last(&pwrdm->wakeuplat_dev_list); + min_latency = node->prio; + } + + /* Find power state with wakeup latency < minimum constraint. */ + for (new_state = 0x0; new_state < PWRDM_MAX_PWRSTS; new_state++) { + if (min_latency == -1 || + pwrdm->wakeup_lat[new_state] < min_latency) + break; + } + + /* No power state wkuplat met constraint. Keep power domain ON. */ + if (new_state == PWRDM_MAX_PWRSTS) { + new_state = PWRDM_FUNC_PWRST_ON; + ret = -ERANGE; + } + + /* OSWR is not supported, set CSWR instead. TODO: add OSWR support */ + if (new_state == PWRDM_FUNC_PWRST_OSWR) + new_state = PWRDM_FUNC_PWRST_CSWR; + + if (pwrdm->state != new_state) { + if (cpu_is_omap44xx() || cpu_is_omap34xx()) + omap_set_pwrdm_state(pwrdm, new_state); + } + + pr_debug("OMAP PM: %s pwrst: curr= %d, prev= %d next= %d " + "wkuplat_min= %lu, state= %d\n", pwrdm->name, + pwrdm_read_pwrst(pwrdm), pwrdm_read_prev_pwrst(pwrdm), + pwrdm_read_next_pwrst(pwrdm), min_latency, new_state); + + return ret; +} diff --git a/arch/arm/mach-omap2/powerdomain.h b/arch/arm/mach-omap2/powerdomain.h index d23d979b9c34..d9c5fc933171 100644 --- a/arch/arm/mach-omap2/powerdomain.h +++ b/arch/arm/mach-omap2/powerdomain.h @@ -19,11 +19,16 @@ #include <linux/types.h> #include <linux/list.h> +#include <linux/plist.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> #include <linux/atomic.h> #include <plat/cpu.h> +#include "voltage.h" + /* Powerdomain basic power states */ #define PWRDM_POWER_OFF 0x0 #define PWRDM_POWER_RET 0x1 @@ -72,12 +77,23 @@ /* XXX A completely arbitrary number. What is reasonable here? */ #define PWRDM_TRANSITION_BAILOUT 100000 +/* Powerdomain functional power states */ +#define PWRDM_FUNC_PWRST_OFF 0x0 +#define PWRDM_FUNC_PWRST_OSWR 0x1 +#define PWRDM_FUNC_PWRST_CSWR 0x2 +#define PWRDM_FUNC_PWRST_ON 0x3 + +#define PWRDM_MAX_FUNC_PWRSTS 4 + +#define UNSUP_STATE -1 + struct clockdomain; struct powerdomain; /** * struct powerdomain - OMAP powerdomain * @name: Powerdomain name + * @voltdm: voltagedomain containing this powerdomain * @omap_chip: represents the OMAP chip types containing this pwrdm * @prcm_offs: the address offset from CM_BASE/PRM_BASE * @prcm_partition: (OMAP4 only) the PRCM partition ID containing @prcm_offs @@ -89,15 +105,22 @@ struct powerdomain; * @pwrsts_mem_on: Possible memory bank pwrstates when pwrdm in ON * @pwrdm_clkdms: Clockdomains in this powerdomain * @node: list_head linking all powerdomains + * @voltdm_node: list_head linking all powerdomains in a voltagedomain * @state: * @state_counter: * @timer: * @state_timer: - * - * @prcm_partition possible values are defined in mach-omap2/prcm44xx.h. + * @wakeup_lat: Wakeup latencies for possible powerdomain power states + * @wakeuplat_lock: spinlock for plist + * @wakeuplat_dev_list: plist_head linking all devices placing constraint + * @wa * @prcm_partition possible values are defined in mach-omap2/prcm44xx.h. */ struct powerdomain { const char *name; + union { + const char *name; + struct voltagedomain *ptr; + } voltdm; const struct omap_chip_id omap_chip; const s16 prcm_offs; const u8 pwrsts; @@ -109,6 +132,7 @@ struct powerdomain { const u8 prcm_partition; struct clockdomain *pwrdm_clkdms[PWRDM_MAX_CLKDMS]; struct list_head node; + struct list_head voltdm_node; int state; unsigned state_counter[PWRDM_MAX_PWRSTS]; unsigned ret_logic_off_counter; @@ -118,6 +142,16 @@ struct powerdomain { s64 timer; s64 state_timer[PWRDM_MAX_PWRSTS]; #endif + const u32 wakeup_lat[PWRDM_MAX_FUNC_PWRSTS]; + spinlock_t wakeuplat_lock; + struct plist_head wakeuplat_dev_list; + struct mutex wakeuplat_mutex; +}; + +struct wakeuplat_dev_list { + struct device *dev; + unsigned long constraint_us; + struct plist_node node; }; /** @@ -176,6 +210,7 @@ int pwrdm_del_clkdm(struct powerdomain *pwrdm, struct clockdomain *clkdm); int pwrdm_for_each_clkdm(struct powerdomain *pwrdm, int (*fn)(struct powerdomain *pwrdm, struct clockdomain *clkdm)); +struct voltagedomain *pwrdm_get_voltdm(struct powerdomain *pwrdm); int pwrdm_get_mem_bank_count(struct powerdomain *pwrdm); @@ -226,5 +261,10 @@ extern u32 omap2_pwrdm_get_mem_bank_stst_mask(u8 bank); extern struct powerdomain wkup_omap2_pwrdm; extern struct powerdomain gfx_omap2_pwrdm; +int pwrdm_wakeuplat_set_constraint(struct powerdomain *pwrdm, + struct device *dev, unsigned long t); +int pwrdm_wakeuplat_release_constraint(struct powerdomain *pwrdm, + struct device *dev); +int pwrdm_wakeuplat_update_pwrst(struct powerdomain *pwrdm); #endif diff --git a/arch/arm/mach-omap2/powerdomains2xxx_3xxx_data.c b/arch/arm/mach-omap2/powerdomains2xxx_3xxx_data.c index 4210c3399769..2242c8e04c76 100644 --- a/arch/arm/mach-omap2/powerdomains2xxx_3xxx_data.c +++ b/arch/arm/mach-omap2/powerdomains2xxx_3xxx_data.c @@ -70,6 +70,7 @@ struct powerdomain gfx_omap2_pwrdm = { .pwrsts_mem_on = { [0] = PWRSTS_ON, /* MEMONSTATE */ }, + .voltdm = { .name = "core" }, }; struct powerdomain wkup_omap2_pwrdm = { @@ -77,4 +78,5 @@ struct powerdomain wkup_omap2_pwrdm = { .prcm_offs = WKUP_MOD, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP24XX | CHIP_IS_OMAP3430), .pwrsts = PWRSTS_ON, + .voltdm = { .name = "wakeup" }, }; diff --git a/arch/arm/mach-omap2/powerdomains2xxx_data.c b/arch/arm/mach-omap2/powerdomains2xxx_data.c index cc389fb2005d..274f64c2eaca 100644 --- a/arch/arm/mach-omap2/powerdomains2xxx_data.c +++ b/arch/arm/mach-omap2/powerdomains2xxx_data.c @@ -38,6 +38,7 @@ static struct powerdomain dsp_pwrdm = { .pwrsts_mem_on = { [0] = PWRSTS_ON, }, + .voltdm = { .name = "core" }, }; static struct powerdomain mpu_24xx_pwrdm = { @@ -53,6 +54,7 @@ static struct powerdomain mpu_24xx_pwrdm = { .pwrsts_mem_on = { [0] = PWRSTS_ON, }, + .voltdm = { .name = "core" }, }; static struct powerdomain core_24xx_pwrdm = { @@ -71,6 +73,7 @@ static struct powerdomain core_24xx_pwrdm = { [1] = PWRSTS_OFF_RET_ON, /* MEM2ONSTATE */ [2] = PWRSTS_OFF_RET_ON, /* MEM3ONSTATE */ }, + .voltdm = { .name = "core" }, }; @@ -95,6 +98,7 @@ static struct powerdomain mdm_pwrdm = { .pwrsts_mem_on = { [0] = PWRSTS_ON, /* MEMONSTATE */ }, + .voltdm = { .name = "core" }, }; #endif /* CONFIG_SOC_OMAP2430 */ diff --git a/arch/arm/mach-omap2/powerdomains3xxx_data.c b/arch/arm/mach-omap2/powerdomains3xxx_data.c index 469a920a74dc..b91224b8b3bd 100644 --- a/arch/arm/mach-omap2/powerdomains3xxx_data.c +++ b/arch/arm/mach-omap2/powerdomains3xxx_data.c @@ -52,6 +52,13 @@ static struct powerdomain iva2_pwrdm = { [2] = PWRSTS_OFF_ON, [3] = PWRSTS_ON, }, + .voltdm = { .name = "mpu_iva" }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1100, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = 350, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; static struct powerdomain mpu_3xxx_pwrdm = { @@ -68,6 +75,13 @@ static struct powerdomain mpu_3xxx_pwrdm = { .pwrsts_mem_on = { [0] = PWRSTS_OFF_ON, }, + .voltdm = { .name = "mpu_iva" }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 95, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = 45, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* @@ -98,6 +112,13 @@ static struct powerdomain core_3xxx_pre_es3_1_pwrdm = { [0] = PWRSTS_OFF_RET_ON, /* MEM1ONSTATE */ [1] = PWRSTS_OFF_RET_ON, /* MEM2ONSTATE */ }, + .voltdm = { .name = "core" }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 100, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = 60, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; static struct powerdomain core_3xxx_es3_1_pwrdm = { @@ -121,6 +142,13 @@ static struct powerdomain core_3xxx_es3_1_pwrdm = { [0] = PWRSTS_OFF_RET_ON, /* MEM1ONSTATE */ [1] = PWRSTS_OFF_RET_ON, /* MEM2ONSTATE */ }, + .voltdm = { .name = "core" }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 100, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = 60, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; static struct powerdomain dss_pwrdm = { @@ -136,6 +164,13 @@ static struct powerdomain dss_pwrdm = { .pwrsts_mem_on = { [0] = PWRSTS_ON, /* MEMONSTATE */ }, + .voltdm = { .name = "core" }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 70, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = 20, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* @@ -157,6 +192,13 @@ static struct powerdomain sgx_pwrdm = { .pwrsts_mem_on = { [0] = PWRSTS_ON, /* MEMONSTATE */ }, + .voltdm = { .name = "core" }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1000, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; static struct powerdomain cam_pwrdm = { @@ -172,6 +214,13 @@ static struct powerdomain cam_pwrdm = { .pwrsts_mem_on = { [0] = PWRSTS_ON, /* MEMONSTATE */ }, + .voltdm = { .name = "core" }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 850, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = 35, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; static struct powerdomain per_pwrdm = { @@ -187,12 +236,20 @@ static struct powerdomain per_pwrdm = { .pwrsts_mem_on = { [0] = PWRSTS_ON, /* MEMONSTATE */ }, + .voltdm = { .name = "core" }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 200, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = 110, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; static struct powerdomain emu_pwrdm = { .name = "emu_pwrdm", .prcm_offs = OMAP3430_EMU_MOD, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .voltdm = { .name = "core" }, }; static struct powerdomain neon_pwrdm = { @@ -201,6 +258,13 @@ static struct powerdomain neon_pwrdm = { .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), .pwrsts = PWRSTS_OFF_RET_ON, .pwrsts_logic_ret = PWRSTS_RET, + .voltdm = { .name = "mpu_iva" }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 200, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = 35, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; static struct powerdomain usbhost_pwrdm = { @@ -223,36 +287,48 @@ static struct powerdomain usbhost_pwrdm = { .pwrsts_mem_on = { [0] = PWRSTS_ON, /* MEMONSTATE */ }, + .voltdm = { .name = "core" }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 800, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = 150, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; static struct powerdomain dpll1_pwrdm = { .name = "dpll1_pwrdm", .prcm_offs = MPU_MOD, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .voltdm = { .name = "mpu_iva" }, }; static struct powerdomain dpll2_pwrdm = { .name = "dpll2_pwrdm", .prcm_offs = OMAP3430_IVA2_MOD, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .voltdm = { .name = "mpu_iva" }, }; static struct powerdomain dpll3_pwrdm = { .name = "dpll3_pwrdm", .prcm_offs = PLL_MOD, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .voltdm = { .name = "core" }, }; static struct powerdomain dpll4_pwrdm = { .name = "dpll4_pwrdm", .prcm_offs = PLL_MOD, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + .voltdm = { .name = "core" }, }; static struct powerdomain dpll5_pwrdm = { .name = "dpll5_pwrdm", .prcm_offs = PLL_MOD, .omap_chip = OMAP_CHIP_INIT(CHIP_GE_OMAP3430ES2), + .voltdm = { .name = "core" }, }; /* As powerdomains are added or removed above, this list must also be changed */ diff --git a/arch/arm/mach-omap2/powerdomains44xx_data.c b/arch/arm/mach-omap2/powerdomains44xx_data.c index 034a4c783a5a..0d801bca1d1d 100644 --- a/arch/arm/mach-omap2/powerdomains44xx_data.c +++ b/arch/arm/mach-omap2/powerdomains44xx_data.c @@ -1,7 +1,7 @@ /* * OMAP4 Power domains framework * - * Copyright (C) 2009-2010 Texas Instruments, Inc. + * Copyright (C) 2009-2011 Texas Instruments, Inc. * Copyright (C) 2009-2011 Nokia Corporation * * Abhijit Pagare (abhijitpagare@ti.com) @@ -33,6 +33,7 @@ /* core_44xx_pwrdm: CORE power domain */ static struct powerdomain core_44xx_pwrdm = { .name = "core_pwrdm", + .voltdm = { .name = "core" }, .prcm_offs = OMAP4430_PRM_CORE_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -54,11 +55,18 @@ static struct powerdomain core_44xx_pwrdm = { [4] = PWRSTS_ON, /* ducati_unicache */ }, .flags = PWRDM_HAS_LOWPOWERSTATECHANGE, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_OSWR] = 600, + [PWRDM_FUNC_PWRST_CSWR] = 300, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* gfx_44xx_pwrdm: 3D accelerator power domain */ static struct powerdomain gfx_44xx_pwrdm = { .name = "gfx_pwrdm", + .voltdm = { .name = "core" }, .prcm_offs = OMAP4430_PRM_GFX_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -71,11 +79,18 @@ static struct powerdomain gfx_44xx_pwrdm = { [0] = PWRSTS_ON, /* gfx_mem */ }, .flags = PWRDM_HAS_LOWPOWERSTATECHANGE, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1000, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* abe_44xx_pwrdm: Audio back end power domain */ static struct powerdomain abe_44xx_pwrdm = { .name = "abe_pwrdm", + .voltdm = { .name = "iva" }, .prcm_offs = OMAP4430_PRM_ABE_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -91,11 +106,18 @@ static struct powerdomain abe_44xx_pwrdm = { [1] = PWRSTS_ON, /* periphmem */ }, .flags = PWRDM_HAS_LOWPOWERSTATECHANGE, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1000, + [PWRDM_FUNC_PWRST_OSWR] = 600, + [PWRDM_FUNC_PWRST_CSWR] = 300, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* dss_44xx_pwrdm: Display subsystem power domain */ static struct powerdomain dss_44xx_pwrdm = { .name = "dss_pwrdm", + .voltdm = { .name = "core" }, .prcm_offs = OMAP4430_PRM_DSS_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -109,11 +131,18 @@ static struct powerdomain dss_44xx_pwrdm = { [0] = PWRSTS_ON, /* dss_mem */ }, .flags = PWRDM_HAS_LOWPOWERSTATECHANGE, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1000, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = 300, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* tesla_44xx_pwrdm: Tesla processor power domain */ static struct powerdomain tesla_44xx_pwrdm = { .name = "tesla_pwrdm", + .voltdm = { .name = "iva" }, .prcm_offs = OMAP4430_PRM_TESLA_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -131,11 +160,18 @@ static struct powerdomain tesla_44xx_pwrdm = { [2] = PWRSTS_ON, /* tesla_l2 */ }, .flags = PWRDM_HAS_LOWPOWERSTATECHANGE, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1000, + [PWRDM_FUNC_PWRST_OSWR] = 600, + [PWRDM_FUNC_PWRST_CSWR] = 300, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* wkup_44xx_pwrdm: Wake-up power domain */ static struct powerdomain wkup_44xx_pwrdm = { .name = "wkup_pwrdm", + .voltdm = { .name = "wakeup" }, .prcm_offs = OMAP4430_PRM_WKUP_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -152,6 +188,7 @@ static struct powerdomain wkup_44xx_pwrdm = { /* cpu0_44xx_pwrdm: MPU0 processor and Neon coprocessor power domain */ static struct powerdomain cpu0_44xx_pwrdm = { .name = "cpu0_pwrdm", + .voltdm = { .name = "mpu" }, .prcm_offs = OMAP4430_PRCM_MPU_CPU0_INST, .prcm_partition = OMAP4430_PRCM_MPU_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -164,11 +201,18 @@ static struct powerdomain cpu0_44xx_pwrdm = { .pwrsts_mem_on = { [0] = PWRSTS_ON, /* cpu0_l1 */ }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1000, + [PWRDM_FUNC_PWRST_OSWR] = 600, + [PWRDM_FUNC_PWRST_CSWR] = 300, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* cpu1_44xx_pwrdm: MPU1 processor and Neon coprocessor power domain */ static struct powerdomain cpu1_44xx_pwrdm = { .name = "cpu1_pwrdm", + .voltdm = { .name = "mpu" }, .prcm_offs = OMAP4430_PRCM_MPU_CPU1_INST, .prcm_partition = OMAP4430_PRCM_MPU_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -181,11 +225,18 @@ static struct powerdomain cpu1_44xx_pwrdm = { .pwrsts_mem_on = { [0] = PWRSTS_ON, /* cpu1_l1 */ }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1000, + [PWRDM_FUNC_PWRST_OSWR] = 600, + [PWRDM_FUNC_PWRST_CSWR] = 300, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* emu_44xx_pwrdm: Emulation power domain */ static struct powerdomain emu_44xx_pwrdm = { .name = "emu_pwrdm", + .voltdm = { .name = "wakeup" }, .prcm_offs = OMAP4430_PRM_EMU_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -202,6 +253,7 @@ static struct powerdomain emu_44xx_pwrdm = { /* mpu_44xx_pwrdm: Modena processor and the Neon coprocessor power domain */ static struct powerdomain mpu_443x_pwrdm = { .name = "mpu_pwrdm", + .voltdm = { .name = "mpu" }, .prcm_offs = OMAP4430_PRM_MPU_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP4430), @@ -218,10 +270,17 @@ static struct powerdomain mpu_443x_pwrdm = { [1] = PWRSTS_ON, /* mpu_l2 */ [2] = PWRSTS_ON, /* mpu_ram */ }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1000, + [PWRDM_FUNC_PWRST_OSWR] = 600, + [PWRDM_FUNC_PWRST_CSWR] = 300, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; static struct powerdomain mpu_446x_pwrdm = { .name = "mpu_pwrdm", + .voltdm = { .name = "mpu" }, .prcm_offs = OMAP4430_PRM_MPU_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP4460), @@ -236,11 +295,18 @@ static struct powerdomain mpu_446x_pwrdm = { [0] = PWRSTS_ON, /* mpu_l2 */ [1] = PWRSTS_ON, /* mpu_ram */ }, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1000, + [PWRDM_FUNC_PWRST_OSWR] = 600, + [PWRDM_FUNC_PWRST_CSWR] = 300, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* ivahd_44xx_pwrdm: IVA-HD power domain */ static struct powerdomain ivahd_44xx_pwrdm = { .name = "ivahd_pwrdm", + .voltdm = { .name = "iva" }, .prcm_offs = OMAP4430_PRM_IVAHD_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -260,11 +326,18 @@ static struct powerdomain ivahd_44xx_pwrdm = { [3] = PWRSTS_ON, /* tcm2_mem */ }, .flags = PWRDM_HAS_LOWPOWERSTATECHANGE, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1000, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = 300, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* cam_44xx_pwrdm: Camera subsystem power domain */ static struct powerdomain cam_44xx_pwrdm = { .name = "cam_pwrdm", + .voltdm = { .name = "core" }, .prcm_offs = OMAP4430_PRM_CAM_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -277,11 +350,18 @@ static struct powerdomain cam_44xx_pwrdm = { [0] = PWRSTS_ON, /* cam_mem */ }, .flags = PWRDM_HAS_LOWPOWERSTATECHANGE, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1000, + [PWRDM_FUNC_PWRST_OSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_CSWR] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* l3init_44xx_pwrdm: L3 initators pheripherals power domain */ static struct powerdomain l3init_44xx_pwrdm = { .name = "l3init_pwrdm", + .voltdm = { .name = "core" }, .prcm_offs = OMAP4430_PRM_L3INIT_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -295,11 +375,18 @@ static struct powerdomain l3init_44xx_pwrdm = { [0] = PWRSTS_ON, /* l3init_bank1 */ }, .flags = PWRDM_HAS_LOWPOWERSTATECHANGE, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = 1000, + [PWRDM_FUNC_PWRST_OSWR] = 600, + [PWRDM_FUNC_PWRST_CSWR] = 300, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* l4per_44xx_pwrdm: Target peripherals power domain */ static struct powerdomain l4per_44xx_pwrdm = { .name = "l4per_pwrdm", + .voltdm = { .name = "core" }, .prcm_offs = OMAP4430_PRM_L4PER_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -315,6 +402,12 @@ static struct powerdomain l4per_44xx_pwrdm = { [1] = PWRSTS_ON, /* retained_bank */ }, .flags = PWRDM_HAS_LOWPOWERSTATECHANGE, + .wakeup_lat = { + [PWRDM_FUNC_PWRST_OFF] = UNSUP_STATE, + [PWRDM_FUNC_PWRST_OSWR] = 600, + [PWRDM_FUNC_PWRST_CSWR] = 300, + [PWRDM_FUNC_PWRST_ON] = 0, + }, }; /* @@ -323,6 +416,7 @@ static struct powerdomain l4per_44xx_pwrdm = { */ static struct powerdomain always_on_core_44xx_pwrdm = { .name = "always_on_core_pwrdm", + .voltdm = { .name = "core" }, .prcm_offs = OMAP4430_PRM_ALWAYS_ON_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), @@ -332,6 +426,7 @@ static struct powerdomain always_on_core_44xx_pwrdm = { /* cefuse_44xx_pwrdm: Customer efuse controller power domain */ static struct powerdomain cefuse_44xx_pwrdm = { .name = "cefuse_pwrdm", + .voltdm = { .name = "core" }, .prcm_offs = OMAP4430_PRM_CEFUSE_INST, .prcm_partition = OMAP4430_PRM_PARTITION, .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP44XX), diff --git a/arch/arm/mach-omap2/prm-regbits-44xx.h b/arch/arm/mach-omap2/prm-regbits-44xx.h index 2ae607e05fda..175412a07ffc 100644 --- a/arch/arm/mach-omap2/prm-regbits-44xx.h +++ b/arch/arm/mach-omap2/prm-regbits-44xx.h @@ -1071,6 +1071,14 @@ #define OMAP4430_SCLL_SHIFT 8 #define OMAP4430_SCLL_MASK (0xff << 8) +/* Used by PRM_VC_CFG_I2C_CLK */ +#define OMAP4430_HSCLH_SHIFT 16 +#define OMAP4430_HSCLH_MASK (0xff << 16) + +/* Used by PRM_VC_CFG_I2C_CLK */ +#define OMAP4430_HSCLL_SHIFT 24 +#define OMAP4430_HSCLL_MASK (0xff << 24) + /* Used by PRM_RSTST */ #define OMAP4430_SECURE_WDT_RST_SHIFT 4 #define OMAP4430_SECURE_WDT_RST_MASK (1 << 4) diff --git a/arch/arm/mach-omap2/prm2xxx_3xxx.c b/arch/arm/mach-omap2/prm2xxx_3xxx.c index 051213fbc346..3b837634730d 100644 --- a/arch/arm/mach-omap2/prm2xxx_3xxx.c +++ b/arch/arm/mach-omap2/prm2xxx_3xxx.c @@ -20,6 +20,8 @@ #include <plat/cpu.h> #include <plat/prcm.h> +#include "vp.h" + #include "prm2xxx_3xxx.h" #include "cm2xxx_3xxx.h" #include "prm-regbits-24xx.h" @@ -156,3 +158,57 @@ int omap2_prm_deassert_hardreset(s16 prm_mod, u8 rst_shift, u8 st_shift) return (c == MAX_MODULE_HARDRESET_WAIT) ? -EBUSY : 0; } + +/* PRM VP */ + +/* + * struct omap3_vp - OMAP3 VP register access description. + * @tranxdone_status: VP_TRANXDONE_ST bitmask in PRM_IRQSTATUS_MPU reg + */ +struct omap3_vp { + u32 tranxdone_status; +}; + +struct omap3_vp omap3_vp[] = { + [OMAP3_VP_VDD_MPU_ID] = { + .tranxdone_status = OMAP3430_VP1_TRANXDONE_ST_MASK, + }, + [OMAP3_VP_VDD_CORE_ID] = { + .tranxdone_status = OMAP3430_VP2_TRANXDONE_ST_MASK, + }, +}; + +#define MAX_VP_ID ARRAY_SIZE(omap3_vp); + +u32 omap3_prm_vp_check_txdone(u8 vp_id) +{ + struct omap3_vp *vp = &omap3_vp[vp_id]; + u32 irqstatus; + + irqstatus = omap2_prm_read_mod_reg(OCP_MOD, + OMAP3_PRM_IRQSTATUS_MPU_OFFSET); + return irqstatus & vp->tranxdone_status; +} + +void omap3_prm_vp_clear_txdone(u8 vp_id) +{ + struct omap3_vp *vp = &omap3_vp[vp_id]; + + omap2_prm_write_mod_reg(vp->tranxdone_status, + OCP_MOD, OMAP3_PRM_IRQSTATUS_MPU_OFFSET); +} + +u32 omap3_prm_vcvp_read(u8 offset) +{ + return omap2_prm_read_mod_reg(OMAP3430_GR_MOD, offset); +} + +void omap3_prm_vcvp_write(u32 val, u8 offset) +{ + omap2_prm_write_mod_reg(val, OMAP3430_GR_MOD, offset); +} + +u32 omap3_prm_vcvp_rmw(u32 mask, u32 bits, u8 offset) +{ + return omap2_prm_rmw_mod_reg_bits(mask, bits, OMAP3430_GR_MOD, offset); +} diff --git a/arch/arm/mach-omap2/prm2xxx_3xxx.h b/arch/arm/mach-omap2/prm2xxx_3xxx.h index a1fc62a39dbb..cef533df0861 100644 --- a/arch/arm/mach-omap2/prm2xxx_3xxx.h +++ b/arch/arm/mach-omap2/prm2xxx_3xxx.h @@ -303,7 +303,19 @@ extern int omap2_prm_is_hardreset_asserted(s16 prm_mod, u8 shift); extern int omap2_prm_assert_hardreset(s16 prm_mod, u8 shift); extern int omap2_prm_deassert_hardreset(s16 prm_mod, u8 rst_shift, u8 st_shift); +/* OMAP3-specific VP functions */ +u32 omap3_prm_vp_check_txdone(u8 vp_id); +void omap3_prm_vp_clear_txdone(u8 vp_id); + +/* + * OMAP3 access functions for voltage controller (VC) and + * voltage proccessor (VP) in the PRM. + */ +extern u32 omap3_prm_vcvp_read(u8 offset); +extern void omap3_prm_vcvp_write(u32 val, u8 offset); +extern u32 omap3_prm_vcvp_rmw(u32 mask, u32 bits, u8 offset); #endif /* CONFIG_ARCH_OMAP4 */ + #endif /* diff --git a/arch/arm/mach-omap2/prm44xx.c b/arch/arm/mach-omap2/prm44xx.c index a2a04bfa9628..8a3bba35aa05 100644 --- a/arch/arm/mach-omap2/prm44xx.c +++ b/arch/arm/mach-omap2/prm44xx.c @@ -21,8 +21,11 @@ #include <plat/cpu.h> #include <plat/prcm.h> +#include "vp.h" #include "prm44xx.h" #include "prm-regbits-44xx.h" +#include "prcm44xx.h" +#include "prminst44xx.h" /* * Address offset (in bytes) between the reset control and the reset @@ -193,3 +196,71 @@ void omap4_prm_global_warm_sw_reset(void) v = omap4_prm_read_inst_reg(OMAP4430_PRM_DEVICE_INST, OMAP4_RM_RSTCTRL); } + +/* PRM VP */ + +/* + * struct omap4_vp - OMAP4 VP register access description. + * @irqstatus_mpu: offset to IRQSTATUS_MPU register for VP + * @tranxdone_status: VP_TRANXDONE_ST bitmask in PRM_IRQSTATUS_MPU reg + */ +struct omap4_vp { + u32 irqstatus_mpu; + u32 tranxdone_status; +}; + +static struct omap4_vp omap4_vp[] = { + [OMAP4_VP_VDD_MPU_ID] = { + .irqstatus_mpu = OMAP4_PRM_IRQSTATUS_MPU_2_OFFSET, + .tranxdone_status = OMAP4430_VP_MPU_TRANXDONE_ST_MASK, + }, + [OMAP4_VP_VDD_IVA_ID] = { + .irqstatus_mpu = OMAP4_PRM_IRQSTATUS_MPU_OFFSET, + .tranxdone_status = OMAP4430_VP_IVA_TRANXDONE_ST_MASK, + }, + [OMAP4_VP_VDD_CORE_ID] = { + .irqstatus_mpu = OMAP4_PRM_IRQSTATUS_MPU_OFFSET, + .tranxdone_status = OMAP4430_VP_CORE_TRANXDONE_ST_MASK, + }, +}; + +u32 omap4_prm_vp_check_txdone(u8 vp_id) +{ + struct omap4_vp *vp = &omap4_vp[vp_id]; + u32 irqstatus; + + irqstatus = omap4_prminst_read_inst_reg(OMAP4430_PRM_PARTITION, + OMAP4430_PRM_OCP_SOCKET_INST, + vp->irqstatus_mpu); + return irqstatus & vp->tranxdone_status; +} + +void omap4_prm_vp_clear_txdone(u8 vp_id) +{ + struct omap4_vp *vp = &omap4_vp[vp_id]; + + omap4_prminst_write_inst_reg(vp->tranxdone_status, + OMAP4430_PRM_PARTITION, + OMAP4430_PRM_OCP_SOCKET_INST, + vp->irqstatus_mpu); +}; + +u32 omap4_prm_vcvp_read(u8 offset) +{ + return omap4_prminst_read_inst_reg(OMAP4430_PRM_PARTITION, + OMAP4430_PRM_DEVICE_INST, offset); +} + +void omap4_prm_vcvp_write(u32 val, u8 offset) +{ + omap4_prminst_write_inst_reg(val, OMAP4430_PRM_PARTITION, + OMAP4430_PRM_DEVICE_INST, offset); +} + +u32 omap4_prm_vcvp_rmw(u32 mask, u32 bits, u8 offset) +{ + return omap4_prminst_rmw_inst_reg_bits(mask, bits, + OMAP4430_PRM_PARTITION, + OMAP4430_PRM_DEVICE_INST, + offset); +} diff --git a/arch/arm/mach-omap2/prm44xx.h b/arch/arm/mach-omap2/prm44xx.h index 67a0d3feb3f6..026529fbaf7d 100644 --- a/arch/arm/mach-omap2/prm44xx.h +++ b/arch/arm/mach-omap2/prm44xx.h @@ -713,8 +713,8 @@ #define OMAP4430_PRM_VC_VAL_BYPASS OMAP44XX_PRM_REGADDR(OMAP4430_PRM_DEVICE_INST, 0x00a0) #define OMAP4_PRM_VC_CFG_CHANNEL_OFFSET 0x00a4 #define OMAP4430_PRM_VC_CFG_CHANNEL OMAP44XX_PRM_REGADDR(OMAP4430_PRM_DEVICE_INST, 0x00a4) -#define OMAP4_PRM_VC_CFG_I2C_INSTE_OFFSET 0x00a8 -#define OMAP4430_PRM_VC_CFG_I2C_INSTE OMAP44XX_PRM_REGADDR(OMAP4430_PRM_DEVICE_INST, 0x00a8) +#define OMAP4_PRM_VC_CFG_I2C_MODE_OFFSET 0x00a8 +#define OMAP4430_PRM_VC_CFG_I2C_MODE OMAP44XX_PRM_REGADDR(OMAP4430_PRM_DEVICE_INST, 0x00a8) #define OMAP4_PRM_VC_CFG_I2C_CLK_OFFSET 0x00ac #define OMAP4430_PRM_VC_CFG_I2C_CLK OMAP44XX_PRM_REGADDR(OMAP4430_PRM_DEVICE_INST, 0x00ac) #define OMAP4_PRM_SRAM_COUNT_OFFSET 0x00b0 @@ -773,6 +773,18 @@ extern int omap4_prm_deassert_hardreset(void __iomem *rstctrl_reg, u8 shift); extern void omap4_prm_global_warm_sw_reset(void); +/* OMAP4-specific VP functions */ +u32 omap4_prm_vp_check_txdone(u8 vp_id); +void omap4_prm_vp_clear_txdone(u8 vp_id); + +/* + * OMAP4 access functions for voltage controller (VC) and + * voltage proccessor (VP) in the PRM. + */ +extern u32 omap4_prm_vcvp_read(u8 offset); +extern void omap4_prm_vcvp_write(u32 val, u8 offset); +extern u32 omap4_prm_vcvp_rmw(u32 mask, u32 bits, u8 offset); + # endif #endif diff --git a/arch/arm/mach-omap2/serial.c b/arch/arm/mach-omap2/serial.c index 1ac361b7b8cb..6959d6554527 100644 --- a/arch/arm/mach-omap2/serial.c +++ b/arch/arm/mach-omap2/serial.c @@ -57,9 +57,9 @@ * NOTE: By default the serial timeout is disabled as it causes lost characters * over the serial ports. This means that the UART clocks will stay on until * disabled via sysfs. This also causes that any deeper omap sleep states are - * blocked. + * blocked. */ -#define DEFAULT_TIMEOUT 0 +#define DEFAULT_TIMEOUT 1000 #define MAX_UART_HWMOD_NAME_LEN 16 @@ -315,6 +315,12 @@ static void omap_uart_enable_wakeup(struct omap_uart_state *uart) v |= OMAP3_PADCONF_WAKEUPENABLE0; omap_ctrl_writew(v, uart->padconf); } + + if (cpu_is_omap44xx() && uart->padconf) { + u16 v = omap4_ctrl_pad_readw(uart->padconf); + v |= OMAP44XX_PADCONF_WAKEUPENABLE0; + omap4_ctrl_pad_writew(v, uart->padconf); + } } static void omap_uart_disable_wakeup(struct omap_uart_state *uart) @@ -418,8 +424,9 @@ void omap_uart_resume_idle(int num) } /* Check for normal UART wakeup */ - if (__raw_readl(uart->wk_st) & uart->wk_mask) - omap_uart_block_sleep(uart); + if (uart->wk_st && uart->wk_mask) + if (__raw_readl(uart->wk_st) & uart->wk_mask) + omap_uart_block_sleep(uart); return; } } @@ -447,6 +454,10 @@ int omap_uart_can_sleep(void) can_sleep = 0; continue; } + if(omap_uart_active(uart->num, uart->timeout)) { + can_sleep = 0; + continue; + } /* This UART can now safely sleep. */ omap_uart_allow_sleep(uart); @@ -543,7 +554,20 @@ static void omap_uart_idle_init(struct omap_uart_state *uart) uart->wk_en = NULL; uart->wk_st = NULL; uart->wk_mask = 0; - uart->padconf = 0; + switch (uart->num) { + case 0: + uart->padconf = 0x0E4; + break; + case 1: + uart->padconf = 0x11C; + break; + case 2: + uart->padconf = 0x144; + break; + case 3: + uart->padconf = 0x15C; + break; + } } uart->irqflags |= IRQF_SHARED; @@ -843,7 +867,8 @@ void __init omap_serial_init_port(struct omap_board_data *bdata) console_unlock(); - if ((cpu_is_omap34xx() && uart->padconf) || + if (((cpu_is_omap34xx() || cpu_is_omap44xx()) + && uart->padconf) || (uart->wk_en && uart->wk_mask)) { device_init_wakeup(&od->pdev.dev, true); DEV_CREATE_FILE(&od->pdev.dev, &dev_attr_sleep_timeout); diff --git a/arch/arm/mach-omap2/sleep44xx.S b/arch/arm/mach-omap2/sleep44xx.S new file mode 100644 index 000000000000..170374a26f0c --- /dev/null +++ b/arch/arm/mach-omap2/sleep44xx.S @@ -0,0 +1,439 @@ +/* + * OMAP44xx CPU low power powerdown and powerup code. + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Written by Santosh Shilimkar <santosh.shilimkar@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. + */ + +#include <linux/linkage.h> +#include <asm/system.h> +#include <asm/smp_scu.h> +#include <asm/memory.h> +#include <asm/hardware/cache-l2x0.h> + +#include <plat/omap44xx.h> +#include <mach/omap4-common.h> +#include <asm/hardware/cache-l2x0.h> + +#include "omap4-sar-layout.h" + +#ifdef CONFIG_SMP + +/* Masks used for MMU manipulation */ +#define TTRBIT_MASK 0xffffc000 +#define TABLE_INDEX_MASK 0xfff00000 +#define TABLE_ENTRY 0x00000c02 +#define CACHE_DISABLE_MASK 0xffffe7fb +#define TABLE_ADDRESS_OFFSET 0x04 +#define CR_VALUE_OFFSET 0x08 +#define SCU_CLEAR_STATE 0xFCFC +/* + * ============================= + * == CPU suspend entry point == + * ============================= + * + * void omap4_cpu_suspend(unsigned int cpu, unsigned int save_state) + * + * This function code saves the CPU context and performs the CPU + * power down sequence. Calling WFI effectively changes the CPU + * power domains states to the desired target power state. + * + * @cpu : contains cpu id (r0) + * @save_state : contains context save state (r1) + * 0 - No context lost + * 1 - CPUx L1 and logic lost: MPUSS CSWR + * 2 - CPUx L1 and logic lost + GIC lost: MPUSS OSWR + * 3 - CPUx L1 and logic lost + GIC + L2 lost: MPUSS OFF + * @return: This function never returns for CPU OFF and DORMANT power states. + * Post WFI, CPU transitions to DORMANT or OFF power state and on wake-up + * from this follows a full CPU reset path via ROM code to CPU restore code. + * It returns to the caller for CPU INACTIVE and ON power states or in case + * CPU failed to transition to targeted OFF/DORMANT state. + */ + +ENTRY(omap4_cpu_suspend) + stmfd sp!, {r0-r12, lr} @ Save registers on stack + cmp r1, #0x0 + beq do_WFI @ Nothing to save, jump to WFI + mov r5, r0 + mov r6, r1 + bl omap4_get_sar_ram_base + mov r8, r0 + str r6, [r8, #L2X0_OFFSET] @ Store save state + ands r5, r5, #0x0f + orreq r8, r8, #CPU0_SAVE_OFFSET + orrne r8, r8, #CPU1_SAVE_OFFSET + + /* + * Save only needed CPU CP15 registers. VFP, breakpoint, + * performance monitor registers are not saved. Generic + * code suppose to take care of those. + */ + mov r4, sp @ Store sp + mrs r5, spsr @ Store spsr + mov r6, lr @ Store lr + stmia r8!, {r4-r6} + + /* c1 and c2 registers */ + mrc p15, 0, r4, c1, c0, 2 @ CPACR + mrc p15, 0, r5, c2, c0, 0 @ TTBR0 + mrc p15, 0, r6, c2, c0, 1 @ TTBR1 + mrc p15, 0, r7, c2, c0, 2 @ TTBCR + stmia r8!, {r4-r7} + + /* c3 and c10 registers */ + mrc p15, 0, r4, c3, c0, 0 @ DACR + mrc p15, 0, r5, c10, c2, 0 @ PRRR + mrc p15, 0, r6, c10, c2, 1 @ NMRR + stmia r8!,{r4-r6} + + /* c12, c13 and CPSR registers */ + mrc p15, 0, r4, c13, c0, 1 @ Context ID + mrc p15, 0, r5, c13, c0, 2 @ User r/w thread ID + mrc p15, 0, r6, c12, c0, 0 @ Secure or NS VBAR + mrs r7, cpsr @ Store CPSR + stmia r8!, {r4-r7} + + /* c1 control register */ + mrc p15, 0, r4, c1, c0, 0 @ Save control register + stmia r8!, {r4} + + /* + * Flush all data from the L1 data cache before disabling + * SCTLR.C bit. + */ + bl v7_flush_dcache_all + + /* + * Clear the SCTLR.C bit to prevent further data cache + * allocation. Clearing SCTLR.C would make all the data accesses + * strongly ordered and would not hit the cache. + */ + mrc p15, 0, r0, c1, c0, 0 + bic r0, r0, #(1 << 2) @ Disable the C bit + mcr p15, 0, r0, c1, c0, 0 + isb + + /* + * Invalidate L1 data cache. Even though only invalidate is + * necessary exported flush API is used here. Doing clean + * on already clean cache would be almost NOP. + */ + bl v7_flush_dcache_all + + /* + * Switch the CPU from Symmetric Multiprocessing (SMP) mode + * to AsymmetricMultiprocessing (AMP) mode by programming + * the SCU power status to DORMANT or OFF mode. + * This enables the CPU to be taken out of coherency by + * preventing the CPU from receiving cache, TLB, or BTB + * maintenance operations broadcast by other CPUs in the cluster. + */ + bl omap4_get_sar_ram_base + mov r8, r0 + mrc p15, 0, r0, c0, c0, 5 @ Read MPIDR + ands r0, r0, #0x0f + ldreq r1, [r8, #SCU_OFFSET0] + ldrne r1, [r8, #SCU_OFFSET1] + bl omap4_get_scu_base + bl scu_power_mode + isb + dsb + +#ifdef CONFIG_CACHE_L2X0 + /* + * Clean and invalidate the L2 cache. + * Common cache-l2x0.c functions can't be used here since it + * uses spinlocks. We are out of coherency here with data cache + * disabled. The spinlock implementation uses exclusive load/store + * instruction which can fail without data cache being enabled. + * OMAP4 hardware doesn't support exclusive monitor which can + * overcome exclusive access issue. Because of this, CPU can + * lead to deadlock. + */ +l2x_clean_inv: + bl omap4_get_sar_ram_base + mov r8, r0 + ldr r0, [r8, #L2X0_OFFSET] + cmp r0, #3 + bne do_WFI + bl omap4_get_l2cache_base + mov r2, r0 + mov r0, #0xff + str r0, [r2, #L2X0_CLEAN_WAY] +wait: + ldr r0, [r2, #L2X0_CLEAN_WAY] + ands r0, r0, #0xff + bne wait +l2x_sync: + mov r0, #0x0 + str r0, [r2, #L2X0_CACHE_SYNC] +sync: + ldr r0, [r2, #L2X0_CACHE_SYNC] + ands r0, r0, #0x1 + bne sync +#endif + +do_WFI: + bl omap4_get_sar_ram_base + mov r4, r0 + ldr r9, [r4, #OMAP_TYPE_OFFSET] + cmp r9, #0x1 @ Check for HS device + bne scu_gp_set + orr r4, r4, #SCU_OFFSET0 + ldr r0, [r4, #0x04] + ldr r1, [r4, #0x08] + stmfd r13!, {r4-r12, r14} + ldr r12, =0x108 @ SCU power state secure API + smc #0 + ldmfd r13!, {r4-r12, r14} + b skip_gp_set +scu_gp_set: + ldr r3, [r4, #SCU_OFFSET0] + bl omap4_get_sar_ram_base @ Take CPUx out of coherency + mov r2, r0 + str r3, [r2, #0x08] +skip_gp_set: + /* + * Execute an ISB instruction to ensure that all of the + * CP15 register changes have been committed. + */ + isb + + /* + * Execute a barrier instruction to ensure that all cache, + * TLB and branch predictor maintenance operations issued + * by any CPU in the cluster have completed. + */ + dsb + dmb + + /* + * Execute a WFI instruction and wait until the + * STANDBYWFI output is asserted to indicate that the + * CPU is in idle and low power state. + */ + wfi @ Wait For Interrupt + + cmp r9, #0x1 + bne scu_gp_clear + mov r0, #0x00 + mov r1, #0xff + stmfd r13!, {r4-r12, r14} + ldr r12, =0x108 @ SCU power state secure API + smc #0 + ldmfd r13!, {r4-r12, r14} + b skip_gp +scu_gp_clear: + ldr r3, =SCU_CLEAR_STATE + str r3, [r2, #0x08] +skip_gp: + dsb @ Issue a write memory barrier + + + /* + * CPU is here when it failed to enter OFF/DORMANT or + * no low power state was attempted. + */ + mrc p15, 0, r0, c1, c0, 0 + tst r0, #(1 << 2) @ Check C bit enabled? + orreq r0, r0, #(1 << 2) @ Enable the C bit + mcreq p15, 0, r0, c1, c0, 0 + isb + + /* + * Ensure the CPU power state is set to NORMAL in + * SCU power state so that CPU is back in coherency. + * In non-coherent mode CPU can lock-up and lead to + * system deadlock. + */ + bl omap4_get_scu_base + mov r1, #SCU_PM_NORMAL + bl scu_power_mode + isb + dsb + + ldmfd sp!, {r0-r12, pc} @ Restore regs and return +ENDPROC(omap4_cpu_suspend) + +/* + * ============================ + * == CPU resume entry point == + * ============================ + * + * void omap4_cpu_resume(void) + * + * ROM code jumps to this function while waking up from CPU + * OFF or DORMANT state. Physical address of the function is + * stored in the SAR RAM while entering to OFF or DORMANT mode. + */ + +ENTRY(omap4_cpu_resume) +#ifdef CONFIG_CACHE_L2X0 + /* + * Restore the L2 AUXCTRL and enable the L2 cache. + * 0x109 = Program the L2X0 AUXCTRL + * 0x102 = Enable the L2 using L2X0 CTRL + * register r0 contains value to be programmed. + * L2 cache is already invalidate by ROM code as part + * of MPUSS OFF wakeup path. + */ + ldr r2, =OMAP44XX_L2CACHE_BASE + ldr r0, [r2, #L2X0_CTRL] + and r0, #0x0f + cmp r0, #1 + beq skip_l2en @ Skip if already enabled + ldr r3, =OMAP44XX_SAR_RAM_BASE + ldr r0, [r3, #L2X0_AUXCTRL_OFFSET] + ldr r12, =0x109 @ Setup L2 AUXCTRL value + dsb + smc #0 + mov r0, #0x1 + ldr r12, =0x102 @ Enable L2 Cache controller + dsb + smc #0 +skip_l2en: +#endif + + /* + * Check the wakeup cpuid and use appropriate + * SAR BANK location for context restore. + */ + ldr r3, =OMAP44XX_SAR_RAM_BASE + mov r1, #0 + mcr p15, 0, r1, c7, c5, 0 @ Invalidate L1 I + mrc p15, 0, r0, c0, c0, 5 @ MPIDR + ands r0, r0, #0x0f + orreq r3, r3, #CPU0_SAVE_OFFSET + orrne r3, r3, #CPU1_SAVE_OFFSET + + /* Restore cp15 registers */ + ldmia r3!, {r4-r6} + mov sp, r4 @ Restore sp + msr spsr_cxsf, r5 @ Restore spsr + mov lr, r6 @ Restore lr + + /* c1 and c2 registers */ + ldmia r3!, {r4-r7} + mcr p15, 0, r4, c1, c0, 2 @ CPACR + mcr p15, 0, r5, c2, c0, 0 @ TTBR0 + mcr p15, 0, r6, c2, c0, 1 @ TTBR1 + mcr p15, 0, r7, c2, c0, 2 @ TTBCR + + /* c3 and c10 registers */ + ldmia r3!,{r4-r6} + mcr p15, 0, r4, c3, c0, 0 @ DACR + mcr p15, 0, r5, c10, c2, 0 @ PRRR + mcr p15, 0, r6, c10, c2, 1 @ NMRR + + /* c12, c13 and CPSR registers */ + ldmia r3!,{r4-r7} + mcr p15, 0, r4, c13, c0, 1 @ Context ID + mcr p15, 0, r5, c13, c0, 2 @ User r/w thread ID + mrc p15, 0, r6, c12, c0, 0 @ Secure or NS VBAR + msr cpsr, r7 @ store cpsr + + /* + * Enabling MMU here. Page entry needs to be altered + * to create temporary 1:1 map and then resore the entry + * ones MMU is enabled + */ + mrc p15, 0, r7, c2, c0, 2 @ Read TTBRControl + and r7, #0x7 @ Extract N (0:2) to decide + cmp r7, #0x0 @ TTBR0/TTBR1 + beq use_ttbr0 +ttbr_error: + b ttbr_error @ Only N = 0 supported +use_ttbr0: + mrc p15, 0, r2, c2, c0, 0 @ Read TTBR0 + ldr r5, =TTRBIT_MASK + and r2, r5 + mov r4, pc + ldr r5, =TABLE_INDEX_MASK + and r4, r5 @ r4 = 31 to 20 bits of pc + ldr r1, =TABLE_ENTRY + add r1, r1, r4 @ r1 has value of table entry + lsr r4, #18 @ Address of table entry + add r2, r4 @ r2 - location to be modified + + /* Ensure the modified entry makes it to main memory */ +#ifdef CONFIG_CACHE_L2X0 + ldr r5, =OMAP44XX_L2CACHE_BASE + str r2, [r5, #L2X0_CLEAN_INV_LINE_PA] +wait_l2: + ldr r0, [r5, #L2X0_CLEAN_INV_LINE_PA] + ands r0, #1 + bne wait_l2 +#endif + + /* Storing previous entry of location being modified */ + ldr r5, =OMAP44XX_SAR_RAM_BASE + ldr r4, [r2] + str r4, [r5, #MMU_OFFSET] @ Modify the table entry + str r1, [r2] + + /* + * Storing address of entry being modified + * It will be restored after enabling MMU + */ + ldr r5, =OMAP44XX_SAR_RAM_BASE + orr r5, r5, #MMU_OFFSET + str r2, [r5, #TABLE_ADDRESS_OFFSET] + mov r0, #0 + mcr p15, 0, r0, c7, c5, 4 @ Flush prefetch buffer + mcr p15, 0, r0, c7, c5, 6 @ Invalidate BTB + mcr p15, 0, r0, c8, c5, 0 @ Invalidate ITLB + mcr p15, 0, r0, c8, c6, 0 @ Invalidate DTLB + + /* + * Restore control register but don't enable Data caches here. + * Caches will be enabled after restoring MMU table entry. + */ + ldmia r3!, {r4} + str r4, [r5, #CR_VALUE_OFFSET] @ Store previous value of CR + ldr r2, =CACHE_DISABLE_MASK + and r4, r2 + mcr p15, 0, r4, c1, c0, 0 + isb + dsb + ldr r0, =mmu_on_label + bx r0 +mmu_on_label: + /* Set up the per-CPU stacks */ + bl cpu_init + + /* + * Restore the MMU table entry that was modified for + * enabling MMU. + */ + bl omap4_get_sar_ram_base + mov r8, r0 + orr r8, r8, #MMU_OFFSET @ Get address of entry that.. + ldr r2, [r8, #TABLE_ADDRESS_OFFSET] @ was modified + ldr r3, =local_va2pa_offet + add r2, r2, r3 + ldr r0, [r8] @ Get the previous value.. + str r0, [r2] @ which needs to be restored + mov r0, #0 + mcr p15, 0, r0, c7, c1, 6 @ flush TLB and issue barriers + mcr p15, 0, r0, c7, c5, 4 @ Flush prefetch buffer + mcr p15, 0, r0, c7, c5, 6 @ Invalidate BTB + mcr p15, 0, r0, c8, c5, 0 @ Invalidate ITLB + mcr p15, 0, r0, c8, c6, 0 @ Invalidate DTLB + dsb + isb + ldr r0, [r8, #CR_VALUE_OFFSET] @ Restore the Control register + mcr p15, 0, r0, c1, c0, 0 @ with caches enabled. + isb + + ldmfd sp!, {r0-r12, pc} @ restore regs and return + + .equ local_va2pa_offet, (PLAT_PHYS_OFFSET + PAGE_OFFSET) + +ENDPROC(omap4_cpu_resume) + +#endif diff --git a/arch/arm/mach-omap2/smartreflex-class3.c b/arch/arm/mach-omap2/smartreflex-class3.c index f438cf4d847b..4eac1bc6de22 100644 --- a/arch/arm/mach-omap2/smartreflex-class3.c +++ b/arch/arm/mach-omap2/smartreflex-class3.c @@ -32,7 +32,7 @@ static int sr_class3_disable(struct voltagedomain *voltdm, int is_volt_reset) omap_vp_disable(voltdm); sr_disable(voltdm); if (is_volt_reset) - omap_voltage_reset(voltdm); + voltdm_reset(voltdm); return 0; } diff --git a/arch/arm/mach-omap2/smartreflex.c b/arch/arm/mach-omap2/smartreflex.c index 13e24f913dd4..f8c6305390e5 100644 --- a/arch/arm/mach-omap2/smartreflex.c +++ b/arch/arm/mach-omap2/smartreflex.c @@ -62,6 +62,7 @@ static LIST_HEAD(sr_list); static struct omap_sr_class_data *sr_class; static struct omap_sr_pmic_data *sr_pmic_data; +static struct dentry *sr_dbg_dir; static inline void sr_write_reg(struct omap_sr *sr, unsigned offset, u32 value) { @@ -143,7 +144,7 @@ static irqreturn_t sr_interrupt(int irq, void *data) sr_write_reg(sr_info, IRQSTATUS, status); } - if (sr_class->class_type == SR_CLASS2 && sr_class->notify) + if (sr_class->notify) sr_class->notify(sr_info->voltdm, status); return IRQ_HANDLED; @@ -258,9 +259,7 @@ static int sr_late_init(struct omap_sr *sr_info) struct resource *mem; int ret = 0; - if (sr_class->class_type == SR_CLASS2 && - sr_class->notify_flags && sr_info->irq) { - + if (sr_class->notify && sr_class->notify_flags && sr_info->irq) { name = kasprintf(GFP_KERNEL, "sr_%s", sr_info->voltdm->name); if (name == NULL) { ret = -ENOMEM; @@ -270,6 +269,7 @@ static int sr_late_init(struct omap_sr *sr_info) 0, name, (void *)sr_info); if (ret) goto error; + disable_irq(sr_info->irq); } if (pdata && pdata->enable_on_init) @@ -278,16 +278,16 @@ static int sr_late_init(struct omap_sr *sr_info) return ret; error: - iounmap(sr_info->base); - mem = platform_get_resource(sr_info->pdev, IORESOURCE_MEM, 0); - release_mem_region(mem->start, resource_size(mem)); - list_del(&sr_info->node); - dev_err(&sr_info->pdev->dev, "%s: ERROR in registering" - "interrupt handler. Smartreflex will" - "not function as desired\n", __func__); - kfree(name); - kfree(sr_info); - return ret; + iounmap(sr_info->base); + mem = platform_get_resource(sr_info->pdev, IORESOURCE_MEM, 0); + release_mem_region(mem->start, resource_size(mem)); + list_del(&sr_info->node); + dev_err(&sr_info->pdev->dev, "%s: ERROR in registering" + "interrupt handler. Smartreflex will" + "not function as desired\n", __func__); + kfree(name); + kfree(sr_info); + return ret; } static void sr_v1_disable(struct omap_sr *sr) @@ -808,10 +808,13 @@ static int omap_sr_autocomp_store(void *data, u64 val) return -EINVAL; } - if (!val) - sr_stop_vddautocomp(sr_info); - else - sr_start_vddautocomp(sr_info); + /* control enable/disable only if there is a delta in value */ + if (sr_info->autocomp_active != val) { + if (!val) + sr_stop_vddautocomp(sr_info); + else + sr_start_vddautocomp(sr_info); + } return 0; } @@ -824,9 +827,10 @@ static int __init omap_sr_probe(struct platform_device *pdev) struct omap_sr *sr_info = kzalloc(sizeof(struct omap_sr), GFP_KERNEL); struct omap_sr_data *pdata = pdev->dev.platform_data; struct resource *mem, *irq; - struct dentry *vdd_dbg_dir, *nvalue_dir; + struct dentry *nvalue_dir; struct omap_volt_data *volt_data; int i, ret = 0; + char *name; if (!sr_info) { dev_err(&pdev->dev, "%s: unable to allocate sr_info\n", @@ -847,6 +851,14 @@ static int __init omap_sr_probe(struct platform_device *pdev) goto err_free_devinfo; } + mem = request_mem_region(mem->start, resource_size(mem), + dev_name(&pdev->dev)); + if (!mem) { + dev_err(&pdev->dev, "%s: no mem region\n", __func__); + ret = -EBUSY; + goto err_free_devinfo; + } + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); pm_runtime_enable(&pdev->dev); @@ -883,28 +895,35 @@ static int __init omap_sr_probe(struct platform_device *pdev) ret = sr_late_init(sr_info); if (ret) { pr_warning("%s: Error in SR late init\n", __func__); - goto err_release_region; + return ret; } } dev_info(&pdev->dev, "%s: SmartReflex driver initialized\n", __func__); - - /* - * If the voltage domain debugfs directory is not created, do - * not try to create rest of the debugfs entries. - */ - vdd_dbg_dir = omap_voltage_get_dbgdir(sr_info->voltdm); - if (!vdd_dbg_dir) { - ret = -EINVAL; - goto err_release_region; + if (!sr_dbg_dir) { + sr_dbg_dir = debugfs_create_dir("smartreflex", NULL); + if (!sr_dbg_dir) { + ret = PTR_ERR(sr_dbg_dir); + pr_err("%s:sr debugfs dir creation failed(%d)\n", + __func__, ret); + goto err_iounmap; + } } - sr_info->dbg_dir = debugfs_create_dir("smartreflex", vdd_dbg_dir); + name = kasprintf(GFP_KERNEL, "sr_%s", sr_info->voltdm->name); + if (!name) { + dev_err(&pdev->dev, "%s: Unable to alloc debugfs name\n", + __func__); + ret = -ENOMEM; + goto err_iounmap; + } + sr_info->dbg_dir = debugfs_create_dir(name, sr_dbg_dir); + kfree(name); if (IS_ERR(sr_info->dbg_dir)) { dev_err(&pdev->dev, "%s: Unable to create debugfs directory\n", __func__); ret = PTR_ERR(sr_info->dbg_dir); - goto err_release_region; + goto err_iounmap; } (void) debugfs_create_file("autocomp", S_IRUGO | S_IWUSR, @@ -921,7 +940,7 @@ static int __init omap_sr_probe(struct platform_device *pdev) dev_err(&pdev->dev, "%s: Unable to create debugfs directory" "for n-values\n", __func__); ret = PTR_ERR(nvalue_dir); - goto err_release_region; + goto err_debugfs; } omap_voltage_get_volttable(sr_info->voltdm, &volt_data); @@ -931,7 +950,7 @@ static int __init omap_sr_probe(struct platform_device *pdev) "entries for n-values\n", __func__, sr_info->voltdm->name); ret = -ENODATA; - goto err_release_region; + goto err_debugfs; } for (i = 0; i < sr_info->nvalue_count; i++) { @@ -945,6 +964,11 @@ static int __init omap_sr_probe(struct platform_device *pdev) return ret; +err_debugfs: + debugfs_remove_recursive(sr_info->dbg_dir); +err_iounmap: + list_del(&sr_info->node); + iounmap(sr_info->base); err_release_region: release_mem_region(mem->start, resource_size(mem)); err_free_devinfo: diff --git a/arch/arm/mach-omap2/smartreflex.h b/arch/arm/mach-omap2/smartreflex.h index 5f35b9e25556..fd61498f71e0 100644 --- a/arch/arm/mach-omap2/smartreflex.h +++ b/arch/arm/mach-omap2/smartreflex.h @@ -152,6 +152,15 @@ struct omap_sr_pmic_data { void (*sr_pmic_init) (void); }; +/** + * struct omap_smartreflex_dev_attr - Smartreflex Device attribute. + * + * @sensor_voltdm_name: Name of voltdomain of SR instance + */ +struct omap_smartreflex_dev_attr { + const char *sensor_voltdm_name; +}; + #ifdef CONFIG_OMAP_SMARTREFLEX /* * The smart reflex driver supports CLASS1 CLASS2 and CLASS3 SR. diff --git a/arch/arm/mach-omap2/sr_device.c b/arch/arm/mach-omap2/sr_device.c index 10d3c5ee8018..0b74f5bdfdad 100644 --- a/arch/arm/mach-omap2/sr_device.c +++ b/arch/arm/mach-omap2/sr_device.c @@ -82,6 +82,7 @@ static int sr_dev_init(struct omap_hwmod *oh, void *user) struct omap_sr_data *sr_data; struct omap_device *od; struct omap_volt_data *volt_data; + struct omap_smartreflex_dev_attr *sr_dev_attr; char *name = "smartreflex"; static int i; @@ -92,9 +93,11 @@ static int sr_dev_init(struct omap_hwmod *oh, void *user) return -ENOMEM; } - if (!oh->vdd_name) { + sr_dev_attr = (struct omap_smartreflex_dev_attr *)oh->dev_attr; + if (!sr_dev_attr || !sr_dev_attr->sensor_voltdm_name) { pr_err("%s: No voltage domain specified for %s." - "Cannot initialize\n", __func__, oh->name); + "Cannot initialize\n", __func__, + oh->name); goto exit; } @@ -102,10 +105,10 @@ static int sr_dev_init(struct omap_hwmod *oh, void *user) sr_data->senn_mod = 0x1; sr_data->senp_mod = 0x1; - sr_data->voltdm = omap_voltage_domain_lookup(oh->vdd_name); + sr_data->voltdm = voltdm_lookup(sr_dev_attr->sensor_voltdm_name); if (IS_ERR(sr_data->voltdm)) { pr_err("%s: Unable to get voltage domain pointer for VDD %s\n", - __func__, oh->vdd_name); + __func__, sr_dev_attr->sensor_voltdm_name); goto exit; } diff --git a/arch/arm/mach-omap2/usb-musb.c b/arch/arm/mach-omap2/usb-musb.c index 35559f77e2de..500d9dbd87a2 100644 --- a/arch/arm/mach-omap2/usb-musb.c +++ b/arch/arm/mach-omap2/usb-musb.c @@ -169,5 +169,7 @@ void __init usb_musb_init(struct omap_musb_board_data *board_data) #else void __init usb_musb_init(struct omap_musb_board_data *board_data) { + if (cpu_is_omap44xx()) + omap4430_phy_init(NULL); } #endif /* CONFIG_USB_MUSB_SOC */ diff --git a/arch/arm/mach-omap2/vc.c b/arch/arm/mach-omap2/vc.c new file mode 100644 index 000000000000..efb077514091 --- /dev/null +++ b/arch/arm/mach-omap2/vc.c @@ -0,0 +1,415 @@ +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/init.h> + +#include <plat/cpu.h> + +#include "voltage.h" +#include "vc.h" +#include "prm-regbits-34xx.h" +#include "prm-regbits-44xx.h" +#include "prm44xx.h" + +/** + * omap_vc_config_channel - configure VC channel to PMIC mappings + * @voltdm: pointer to voltagdomain defining the desired VC channel + * + * Configures the VC channel to PMIC mappings for the following + * PMIC settings + * - i2c slave address (SA) + * - voltage configuration address (RAV) + * - command configuration address (RAC) and enable bit (RACEN) + * - command values for ON, ONLP, RET and OFF (CMD) + * + * This function currently only allows flexible configuration of + * the non-default channels. Introduced in OMAP4, is a concept of a default + * channel - in OMAP4, this channel is MPU, all other domains could optionally + * link their i2c regs to use MPU channel's configuration if required. + */ +static int omap_vc_config_channel(struct voltagedomain *voltdm) +{ + struct omap_vc_channel *vc = voltdm->vc; + + /* + * For default channel, the only configurable bit is RACEN. + * All others default channel bits stays at zero + * (see function comment above.) + */ + if (vc->is_default_channel) + vc->cfg_channel &= CFG_CHANNEL_RACEN; + + voltdm->rmw(CFG_CHANNEL_MASK << vc->cfg_channel_sa_shift, + vc->cfg_channel << vc->cfg_channel_sa_shift, + vc->common->cfg_channel_reg); + + return 0; +} + +/* Voltage scale and accessory APIs */ +int omap_vc_pre_scale(struct voltagedomain *voltdm, + unsigned long target_volt, + u8 *target_vsel, u8 *current_vsel) +{ + struct omap_vc_channel *vc = voltdm->vc; + u32 vc_cmdval; + + /* Check if sufficient pmic info is available for this vdd */ + if (!voltdm->pmic) { + pr_err("%s: Insufficient pmic info to scale the vdd_%s\n", + __func__, voltdm->name); + return -EINVAL; + } + + if (!voltdm->pmic->uv_to_vsel) { + pr_err("%s: PMIC function to convert voltage in uV to" + "vsel not registered. Hence unable to scale voltage" + "for vdd_%s\n", __func__, voltdm->name); + return -ENODATA; + } + + if (!voltdm->read || !voltdm->write) { + pr_err("%s: No read/write API for accessing vdd_%s regs\n", + __func__, voltdm->name); + return -EINVAL; + } + + *target_vsel = voltdm->pmic->uv_to_vsel(target_volt); + *current_vsel = voltdm->read(voltdm->vp->voltage); + + /* Setting the ON voltage to the new target voltage */ + vc_cmdval = voltdm->read(vc->cmdval_reg); + vc_cmdval &= ~vc->common->cmd_on_mask; + vc_cmdval |= (*target_vsel << vc->common->cmd_on_shift); + voltdm->write(vc_cmdval, vc->cmdval_reg); + + omap_vp_update_errorgain(voltdm, target_volt); + + return 0; +} + +void omap_vc_post_scale(struct voltagedomain *voltdm, + unsigned long target_volt, + u8 target_vsel, u8 current_vsel) +{ + u32 smps_steps = 0, smps_delay = 0; + + smps_steps = abs(target_vsel - current_vsel); + /* SMPS slew rate / step size. 2us added as buffer. */ + smps_delay = ((smps_steps * voltdm->pmic->step_size) / + voltdm->pmic->slew_rate) + 2; + udelay(smps_delay); + + voltdm->curr_volt = target_volt; +} + +static int omap_vc_bypass_send_value(struct voltagedomain *voltdm, + struct omap_vc_channel *vc, u8 sa, u8 reg, u32 data) +{ + u32 loop_cnt = 0, retries_cnt = 0; + u32 vc_valid, vc_bypass_val_reg, vc_bypass_value; + + if (IS_ERR_OR_NULL(vc->common)) { + pr_err("%s voldm=%s bad value for vc->common\n", + __func__, voltdm->name); + return -EINVAL; + } + + vc_valid = vc->common->valid; + vc_bypass_val_reg = vc->common->bypass_val_reg; + vc_bypass_value = (data << vc->common->data_shift) | + (reg << vc->common->regaddr_shift) | + (sa << vc->common->slaveaddr_shift); + + voltdm->write(vc_bypass_value, vc_bypass_val_reg); + voltdm->write(vc_bypass_value | vc_valid, vc_bypass_val_reg); + + vc_bypass_value = voltdm->read(vc_bypass_val_reg); + + /* + * Loop till the bypass command is acknowledged from the SMPS. + * NOTE: This is legacy code. The loop count and retry count needs + * to be revisited. + */ + while (!(vc_bypass_value & vc_valid)) { + loop_cnt++; + + if (retries_cnt > 10) { + pr_warning("%s: Retry count exceeded\n", __func__); + return -ETIMEDOUT; + } + + if (loop_cnt > 50) { + retries_cnt++; + loop_cnt = 0; + udelay(10); + } + vc_bypass_value = voltdm->read(vc_bypass_val_reg); + } + + return 0; + +} + +/* vc_bypass_scale_voltage - VC bypass method of voltage scaling */ +int omap_vc_bypass_scale_voltage(struct voltagedomain *voltdm, + unsigned long target_volt) +{ + struct omap_vc_channel *vc; + u8 target_vsel, current_vsel; + int ret; + + if (IS_ERR_OR_NULL(voltdm)) { + pr_err("%s bad voldm\n", __func__); + return -EINVAL; + } + + vc = voltdm->vc; + if (IS_ERR_OR_NULL(vc)) { + pr_err("%s voldm=%s bad vc\n", __func__, voltdm->name); + return -EINVAL; + } + + ret = omap_vc_pre_scale(voltdm, target_volt, &target_vsel, ¤t_vsel); + if (ret) + return ret; + + ret = omap_vc_bypass_send_value(voltdm, vc, vc->i2c_slave_addr, + vc->volt_reg_addr, target_vsel); + if (ret) + return ret; + + omap_vc_post_scale(voltdm, target_volt, target_vsel, current_vsel); + return 0; +} + +/** + * omap_vc_bypass_send_i2c_msg() - Function to control PMIC registers over SRI2C + * @voltdm: voltage domain + * @slave_addr: slave address of the device. + * @reg_addr: register address to access + * @data: what do we want to write there + * + * Many simpler PMICs with a single I2C interface still have configuration + * registers that may need population. Typical being slew rate configurations + * thermal shutdown configuration etc. When these PMICs are hooked on I2C_SR, + * this function allows these configuration registers to be accessed. + * + * WARNING: Though this could be used for voltage register configurations over + * I2C_SR, DONOT use it for that purpose, all the Voltage controller's internal + * information is bypassed using this function and must be used judiciously. + */ +int omap_vc_bypass_send_i2c_msg(struct voltagedomain *voltdm, u8 slave_addr, + u8 reg_addr, u8 data) +{ + struct omap_vc_channel *vc; + + if (IS_ERR_OR_NULL(voltdm)) { + pr_err("%s bad voldm\n", __func__); + return -EINVAL; + } + + vc = voltdm->vc; + if (IS_ERR_OR_NULL(vc)) { + pr_err("%s voldm=%s bad vc\n", __func__, voltdm->name); + return -EINVAL; + } + + return omap_vc_bypass_send_value(voltdm, vc, slave_addr, + reg_addr, data); +} + +static void __init omap3_vfsm_init(struct voltagedomain *voltdm) +{ + /* + * Voltage Manager FSM parameters init + * XXX This data should be passed in from the board file + */ + voltdm->write(OMAP3_CLKSETUP, OMAP3_PRM_CLKSETUP_OFFSET); + voltdm->write(OMAP3_VOLTOFFSET, OMAP3_PRM_VOLTOFFSET_OFFSET); + voltdm->write(OMAP3_VOLTSETUP2, OMAP3_PRM_VOLTSETUP2_OFFSET); +} + +static void __init omap3_vc_init_channel(struct voltagedomain *voltdm) +{ + static bool is_initialized; + + if (is_initialized) + return; + + omap3_vfsm_init(voltdm); + + is_initialized = true; +} + + +/* OMAP4 specific voltage init functions */ +static void __init omap4_vc_init_channel(struct voltagedomain *voltdm) +{ + static bool is_initialized; + struct omap_voltdm_pmic *pmic = voltdm->pmic; + u32 vc_val = 0; + + if (is_initialized) + return; + + if (pmic->i2c_high_speed) { + vc_val |= pmic->i2c_hscll_low << OMAP4430_HSCLL_SHIFT; + vc_val |= pmic->i2c_hscll_high << OMAP4430_HSCLH_SHIFT; + } + + vc_val |= pmic->i2c_scll_low << OMAP4430_SCLL_SHIFT; + vc_val |= pmic->i2c_scll_high << OMAP4430_SCLH_SHIFT; + + if (vc_val) + voltdm->write(vc_val, OMAP4_PRM_VC_CFG_I2C_CLK_OFFSET); + + is_initialized = true; +} + +/** + * omap_vc_i2c_init - initialize I2C interface to PMIC + * @voltdm: voltage domain containing VC data + * + * Use PMIC supplied seetings for I2C high-speed mode and + * master code (if set) and program the VC I2C configuration + * register. + * + * The VC I2C configuration is common to all VC channels, + * so this function only configures I2C for the first VC + * channel registers. All other VC channels will use the + * same configuration. + */ +static void __init omap_vc_i2c_init(struct voltagedomain *voltdm) +{ + struct omap_vc_channel *vc = voltdm->vc; + static bool initialized; + static bool i2c_high_speed; + u8 mcode; + + if (initialized) { + if (voltdm->pmic->i2c_high_speed != i2c_high_speed) + pr_warn("%s: I2C config for all channels must match.", + __func__); + return; + } + + i2c_high_speed = voltdm->pmic->i2c_high_speed; + if (i2c_high_speed) + voltdm->rmw(vc->common->i2c_cfg_hsen_mask, + vc->common->i2c_cfg_hsen_mask, + vc->common->i2c_cfg_reg); + + mcode = voltdm->pmic->i2c_mcode; + if (mcode) + voltdm->rmw(vc->common->i2c_mcode_mask, + mcode << __ffs(vc->common->i2c_mcode_mask), + vc->common->i2c_cfg_reg); + + initialized = true; +} + +void __init omap_vc_init_channel(struct voltagedomain *voltdm) +{ + struct omap_vc_channel *vc = voltdm->vc; + u8 on_vsel, onlp_vsel, ret_vsel, off_vsel; + struct omap_vc_channel_cfg *cfg_channel_data; + u32 val; + + if (!voltdm->pmic || !voltdm->pmic->uv_to_vsel) { + pr_err("%s: PMIC info requried to configure vc for" + "vdd_%s not populated.Hence cannot initialize vc\n", + __func__, voltdm->name); + return; + } + + if (!voltdm->read || !voltdm->write) { + pr_err("%s: No read/write API for accessing vdd_%s regs\n", + __func__, voltdm->name); + return; + } + + /* Ensure we have VC channel data */ + if (!vc->cfg_ch_bits) { + pr_err("%s: No CFG Channel data for vdd_%s regs\n", + __func__, voltdm->name); + return; + } + cfg_channel_data = vc->cfg_ch_bits; + + /* Dont proceed if channel configurations are messed up */ + if (vc->is_default_channel && + (vc->i2c_slave_addr == USE_DEFAULT_CHANNEL_I2C_PARAM || + vc->cmd_reg_addr == USE_DEFAULT_CHANNEL_I2C_PARAM || + vc->volt_reg_addr == USE_DEFAULT_CHANNEL_I2C_PARAM)) { + pr_err("%s: Voltage Domain %s is default.INVALID CONFIGURATION:" + "slave_add = %04x cmd=%04x vol=%04x!\n", + __func__, voltdm->name, vc->i2c_slave_addr, + vc->cmd_reg_addr, vc->volt_reg_addr); + return; + } + + vc->cfg_channel = 0; + + /* get PMIC/board specific settings */ + vc->i2c_slave_addr = voltdm->pmic->i2c_slave_addr; + vc->volt_reg_addr = voltdm->pmic->volt_reg_addr; + vc->cmd_reg_addr = voltdm->pmic->cmd_reg_addr; + vc->setup_time = voltdm->pmic->volt_setup_time; + + /* Configure the I2C slave address for this VC */ + if (vc->i2c_slave_addr != USE_DEFAULT_CHANNEL_I2C_PARAM) { + voltdm->rmw(vc->smps_sa_mask, + vc->i2c_slave_addr << __ffs(vc->smps_sa_mask), + vc->common->smps_sa_reg); + vc->cfg_channel |= cfg_channel_data->sa; + } + + /* + * Configure the PMIC register addresses. + */ + if (vc->cmd_reg_addr != USE_DEFAULT_CHANNEL_I2C_PARAM) { + voltdm->rmw(vc->smps_cmdra_mask, + vc->cmd_reg_addr << __ffs(vc->smps_cmdra_mask), + vc->common->smps_cmdra_reg); + vc->cfg_channel |= cfg_channel_data->rac; + } + + /* if voltage and cmd regs are same, we can use cmdra register */ + if (vc->volt_reg_addr == vc->cmd_reg_addr) { + vc->cfg_channel |= cfg_channel_data->racen; + } + if (vc->volt_reg_addr != USE_DEFAULT_CHANNEL_I2C_PARAM) { + voltdm->rmw(vc->smps_volra_mask, + vc->volt_reg_addr << __ffs(vc->smps_volra_mask), + vc->common->smps_volra_reg); + vc->cfg_channel |= cfg_channel_data->rav; + } + + /* Set up the on, inactive, retention and off voltage */ + on_vsel = voltdm->pmic->uv_to_vsel(voltdm->pmic->on_volt); + onlp_vsel = voltdm->pmic->uv_to_vsel(voltdm->pmic->onlp_volt); + ret_vsel = voltdm->pmic->uv_to_vsel(voltdm->pmic->ret_volt); + off_vsel = voltdm->pmic->uv_to_vsel(voltdm->pmic->off_volt); + val = ((on_vsel << vc->common->cmd_on_shift) | + (onlp_vsel << vc->common->cmd_onlp_shift) | + (ret_vsel << vc->common->cmd_ret_shift) | + (off_vsel << vc->common->cmd_off_shift)); + voltdm->write(val, vc->cmdval_reg); + vc->cfg_channel |= cfg_channel_data->cmd; + + /* Channel configuration */ + omap_vc_config_channel(voltdm); + + /* Configure the setup times */ + voltdm->rmw(voltdm->vfsm->voltsetup_mask, + vc->setup_time << __ffs(voltdm->vfsm->voltsetup_mask), + voltdm->vfsm->voltsetup_reg); + + omap_vc_i2c_init(voltdm); + + if (cpu_is_omap34xx()) + omap3_vc_init_channel(voltdm); + else if (cpu_is_omap44xx()) + omap4_vc_init_channel(voltdm); +} + diff --git a/arch/arm/mach-omap2/vc.h b/arch/arm/mach-omap2/vc.h index e7767771de49..3af5336aedbc 100644 --- a/arch/arm/mach-omap2/vc.h +++ b/arch/arm/mach-omap2/vc.h @@ -19,8 +19,10 @@ #include <linux/kernel.h> +struct voltagedomain; + /** - * struct omap_vc_common_data - per-VC register/bitfield data + * struct omap_vc_common - per-VC register/bitfield data * @cmd_on_mask: ON bitmask in PRM_VC_CMD_VAL* register * @valid: VALID bitmask in PRM_VC_BYPASS_VAL register * @smps_sa_reg: Offset of PRM_VC_SMPS_SA reg from PRM start @@ -33,15 +35,19 @@ * @cmd_onlp_shift: ONLP field shift in PRM_VC_CMD_VAL_* register * @cmd_ret_shift: RET field shift in PRM_VC_CMD_VAL_* register * @cmd_off_shift: OFF field shift in PRM_VC_CMD_VAL_* register + * @i2c_cfg_reg: I2C configuration register offset + * @i2c_cfg_hsen_mask: high-speed mode bit field mask in I2C config register + * @i2c_mcode_mask: MCODE field mask for I2C config register * * XXX One of cmd_on_mask and cmd_on_shift are not needed * XXX VALID should probably be a shift, not a mask */ -struct omap_vc_common_data { +struct omap_vc_common { u32 cmd_on_mask; u32 valid; u8 smps_sa_reg; u8 smps_volra_reg; + u8 smps_cmdra_reg; u8 bypass_val_reg; u8 data_shift; u8 slaveaddr_shift; @@ -50,34 +56,84 @@ struct omap_vc_common_data { u8 cmd_onlp_shift; u8 cmd_ret_shift; u8 cmd_off_shift; + u8 cfg_channel_reg; + u8 i2c_cfg_reg; + u8 i2c_cfg_hsen_mask; + u8 i2c_mcode_mask; }; +/* + * Channel configuration bits, common for OMAP3 & 4 + * OMAP3 register: PRM_VC_CH_CONF + * OMAP4 register: PRM_VC_CFG_CHANNEL + */ +#define CFG_CHANNEL_SA BIT(0) +#define CFG_CHANNEL_RAV BIT(1) +#define CFG_CHANNEL_RAC BIT(2) +#define CFG_CHANNEL_RACEN BIT(3) +#define CFG_CHANNEL_CMD BIT(4) +#define CFG_CHANNEL_MASK 0x3f /** - * struct omap_vc_instance_data - VC per-instance data - * @vc_common: pointer to VC common data for this platform - * @smps_sa_mask: SA* bitmask in the PRM_VC_SMPS_SA register + * struct omap_vc_channel_cfg - describe the cfg_channel bitfield + * @sa: SA_VDD_xxx_L + * @rav: RAV_VDD_xxx_L + * @rac: RAC_VDD_xxx_L + * @racen: RACEN_VDD_xxx_L + * @cmd: CMD_VDD_xxx_L + */ +struct omap_vc_channel_cfg { + u8 sa; + u8 rav; + u8 rac; + u8 racen; + u8 cmd; +}; + +/** + * struct omap_vc_channel - VC per-instance data + * @common: pointer to VC common data for this platform + * @is_master_channel: if the channel is the master channel + * @smps_sa_mask: i2c slave address bitmask in the PRM_VC_SMPS_SA register * @smps_volra_mask: VOLRA* bitmask in the PRM_VC_VOL_RA register - * @smps_sa_shift: SA* field shift in the PRM_VC_SMPS_SA register - * @smps_volra_shift: VOLRA* field shift in the PRM_VC_VOL_RA register - * - * XXX It is not necessary to have both a *_mask and a *_shift - - * remove one + * @cfg_ch_bits: exception handling for unordered register bits in cfg_channel */ -struct omap_vc_instance_data { - const struct omap_vc_common_data *vc_common; +struct omap_vc_channel { + /* channel state */ + u16 i2c_slave_addr; + u16 volt_reg_addr; + u16 cmd_reg_addr; + u8 cfg_channel; + struct omap_vc_channel_cfg *cfg_ch_bits; + u16 setup_time; + bool i2c_high_speed; + + /* register access data */ + const struct omap_vc_common *common; + bool is_default_channel; u32 smps_sa_mask; u32 smps_volra_mask; + u32 smps_cmdra_mask; u8 cmdval_reg; - u8 smps_sa_shift; - u8 smps_volra_shift; + u8 cfg_channel_sa_shift; }; -extern struct omap_vc_instance_data omap3_vc1_data; -extern struct omap_vc_instance_data omap3_vc2_data; +extern struct omap_vc_channel omap3_vc_mpu; +extern struct omap_vc_channel omap3_vc_core; -extern struct omap_vc_instance_data omap4_vc_mpu_data; -extern struct omap_vc_instance_data omap4_vc_iva_data; -extern struct omap_vc_instance_data omap4_vc_core_data; +extern struct omap_vc_channel omap4_vc_mpu; +extern struct omap_vc_channel omap4_vc_iva; +extern struct omap_vc_channel omap4_vc_core; +void omap_vc_init_channel(struct voltagedomain *voltdm); +int omap_vc_pre_scale(struct voltagedomain *voltdm, + unsigned long target_volt, + u8 *target_vsel, u8 *current_vsel); +void omap_vc_post_scale(struct voltagedomain *voltdm, + unsigned long target_volt, + u8 target_vsel, u8 current_vsel); +int omap_vc_bypass_scale_voltage(struct voltagedomain *voltdm, + unsigned long target_volt); +int omap_vc_bypass_send_i2c_msg(struct voltagedomain *voltdm, + u8 slave_addr, u8 reg_addr, u8 data); #endif diff --git a/arch/arm/mach-omap2/vc3xxx_data.c b/arch/arm/mach-omap2/vc3xxx_data.c index f37dc4bc379a..0a22b1d9d8ff 100644 --- a/arch/arm/mach-omap2/vc3xxx_data.c +++ b/arch/arm/mach-omap2/vc3xxx_data.c @@ -29,9 +29,10 @@ * VC data common to 34xx/36xx chips * XXX This stuff presumably belongs in the vc3xxx.c or vc.c file. */ -static struct omap_vc_common_data omap3_vc_common = { +static struct omap_vc_common omap3_vc_common = { .smps_sa_reg = OMAP3_PRM_VC_SMPS_SA_OFFSET, .smps_volra_reg = OMAP3_PRM_VC_SMPS_VOL_RA_OFFSET, + .smps_cmdra_reg = OMAP3_PRM_VC_SMPS_CMD_RA_OFFSET, .bypass_val_reg = OMAP3_PRM_VC_BYPASS_VAL_OFFSET, .data_shift = OMAP3430_DATA_SHIFT, .slaveaddr_shift = OMAP3430_SLAVEADDR_SHIFT, @@ -42,22 +43,37 @@ static struct omap_vc_common_data omap3_vc_common = { .cmd_onlp_shift = OMAP3430_VC_CMD_ONLP_SHIFT, .cmd_ret_shift = OMAP3430_VC_CMD_RET_SHIFT, .cmd_off_shift = OMAP3430_VC_CMD_OFF_SHIFT, + .cfg_channel_reg = OMAP3_PRM_VC_CH_CONF_OFFSET, + .i2c_cfg_hsen_mask = OMAP3430_HSEN_MASK, + .i2c_cfg_reg = OMAP3_PRM_VC_I2C_CFG_OFFSET, + .i2c_mcode_mask = OMAP3430_MCODE_MASK, }; -struct omap_vc_instance_data omap3_vc1_data = { - .vc_common = &omap3_vc_common, +/* for all channels */ +struct omap_vc_channel_cfg vc3xxx_common_cfg_channel = { + .sa = CFG_CHANNEL_SA, + .rav = CFG_CHANNEL_RAV, + .rac = CFG_CHANNEL_RAC, + .racen = CFG_CHANNEL_RACEN, + .cmd = CFG_CHANNEL_CMD, +}; + +struct omap_vc_channel omap3_vc_mpu = { + .common = &omap3_vc_common, .cmdval_reg = OMAP3_PRM_VC_CMD_VAL_0_OFFSET, - .smps_sa_shift = OMAP3430_PRM_VC_SMPS_SA_SA0_SHIFT, .smps_sa_mask = OMAP3430_PRM_VC_SMPS_SA_SA0_MASK, - .smps_volra_shift = OMAP3430_VOLRA0_SHIFT, .smps_volra_mask = OMAP3430_VOLRA0_MASK, + .smps_cmdra_mask = OMAP3430_CMDRA0_MASK, + .cfg_channel_sa_shift = OMAP3430_PRM_VC_SMPS_SA_SA0_SHIFT, + .cfg_ch_bits = &vc3xxx_common_cfg_channel, }; -struct omap_vc_instance_data omap3_vc2_data = { - .vc_common = &omap3_vc_common, +struct omap_vc_channel omap3_vc_core = { + .common = &omap3_vc_common, .cmdval_reg = OMAP3_PRM_VC_CMD_VAL_1_OFFSET, - .smps_sa_shift = OMAP3430_PRM_VC_SMPS_SA_SA1_SHIFT, .smps_sa_mask = OMAP3430_PRM_VC_SMPS_SA_SA1_MASK, - .smps_volra_shift = OMAP3430_VOLRA1_SHIFT, .smps_volra_mask = OMAP3430_VOLRA1_MASK, + .smps_cmdra_mask = OMAP3430_CMDRA1_MASK, + .cfg_channel_sa_shift = OMAP3430_PRM_VC_SMPS_SA_SA1_SHIFT, + .cfg_ch_bits = &vc3xxx_common_cfg_channel, }; diff --git a/arch/arm/mach-omap2/vc44xx_data.c b/arch/arm/mach-omap2/vc44xx_data.c index a98da8ddec52..817e87d3a836 100644 --- a/arch/arm/mach-omap2/vc44xx_data.c +++ b/arch/arm/mach-omap2/vc44xx_data.c @@ -30,9 +30,10 @@ * VC data common to 44xx chips * XXX This stuff presumably belongs in the vc3xxx.c or vc.c file. */ -static const struct omap_vc_common_data omap4_vc_common = { +static const struct omap_vc_common omap4_vc_common = { .smps_sa_reg = OMAP4_PRM_VC_SMPS_SA_OFFSET, .smps_volra_reg = OMAP4_PRM_VC_VAL_SMPS_RA_VOL_OFFSET, + .smps_cmdra_reg = OMAP4_PRM_VC_VAL_SMPS_RA_CMD_OFFSET, .bypass_val_reg = OMAP4_PRM_VC_VAL_BYPASS_OFFSET, .data_shift = OMAP4430_DATA_SHIFT, .slaveaddr_shift = OMAP4430_SLAVEADDR_SHIFT, @@ -43,33 +44,59 @@ static const struct omap_vc_common_data omap4_vc_common = { .cmd_onlp_shift = OMAP4430_ONLP_SHIFT, .cmd_ret_shift = OMAP4430_RET_SHIFT, .cmd_off_shift = OMAP4430_OFF_SHIFT, + .cfg_channel_reg = OMAP4_PRM_VC_CFG_CHANNEL_OFFSET, + .i2c_cfg_reg = OMAP4_PRM_VC_CFG_I2C_MODE_OFFSET, + .i2c_cfg_hsen_mask = OMAP4430_HSMODEEN_MASK, + .i2c_mcode_mask = OMAP4430_HSMCODE_MASK, +}; + +/* Handle exception case for vc44xx MPU */ +static struct omap_vc_channel_cfg vc44xx_mpu_cfg_channel = { + .sa = CFG_CHANNEL_SA, + .cmd = BIT(1), + .rav = BIT(2), + .rac = BIT(3), + .racen = BIT(4), +}; + +/* for all other */ +struct omap_vc_channel_cfg vc44xx_common_cfg_channel = { + .sa = CFG_CHANNEL_SA, + .rav = CFG_CHANNEL_RAV, + .rac = CFG_CHANNEL_RAC, + .racen = CFG_CHANNEL_RACEN, + .cmd = CFG_CHANNEL_CMD, }; /* VC instance data for each controllable voltage line */ -struct omap_vc_instance_data omap4_vc_mpu_data = { - .vc_common = &omap4_vc_common, +struct omap_vc_channel omap4_vc_mpu = { + .common = &omap4_vc_common, + .is_default_channel = true, .cmdval_reg = OMAP4_PRM_VC_VAL_CMD_VDD_MPU_L_OFFSET, - .smps_sa_shift = OMAP4430_SA_VDD_MPU_L_PRM_VC_SMPS_SA_SHIFT, .smps_sa_mask = OMAP4430_SA_VDD_MPU_L_PRM_VC_SMPS_SA_MASK, - .smps_volra_shift = OMAP4430_VOLRA_VDD_MPU_L_SHIFT, .smps_volra_mask = OMAP4430_VOLRA_VDD_MPU_L_MASK, + .smps_cmdra_mask = OMAP4430_CMDRA_VDD_MPU_L_MASK, + .cfg_channel_sa_shift = OMAP4430_SA_VDD_MPU_L_SHIFT, + .cfg_ch_bits = &vc44xx_mpu_cfg_channel, }; -struct omap_vc_instance_data omap4_vc_iva_data = { - .vc_common = &omap4_vc_common, +struct omap_vc_channel omap4_vc_iva = { + .common = &omap4_vc_common, .cmdval_reg = OMAP4_PRM_VC_VAL_CMD_VDD_IVA_L_OFFSET, - .smps_sa_shift = OMAP4430_SA_VDD_IVA_L_PRM_VC_SMPS_SA_SHIFT, .smps_sa_mask = OMAP4430_SA_VDD_IVA_L_PRM_VC_SMPS_SA_MASK, - .smps_volra_shift = OMAP4430_VOLRA_VDD_IVA_L_SHIFT, .smps_volra_mask = OMAP4430_VOLRA_VDD_IVA_L_MASK, + .smps_cmdra_mask = OMAP4430_CMDRA_VDD_IVA_L_MASK, + .cfg_channel_sa_shift = OMAP4430_SA_VDD_IVA_L_SHIFT, + .cfg_ch_bits = &vc44xx_common_cfg_channel, }; -struct omap_vc_instance_data omap4_vc_core_data = { - .vc_common = &omap4_vc_common, +struct omap_vc_channel omap4_vc_core = { + .common = &omap4_vc_common, .cmdval_reg = OMAP4_PRM_VC_VAL_CMD_VDD_CORE_L_OFFSET, - .smps_sa_shift = OMAP4430_SA_VDD_CORE_L_0_6_SHIFT, .smps_sa_mask = OMAP4430_SA_VDD_CORE_L_0_6_MASK, - .smps_volra_shift = OMAP4430_VOLRA_VDD_CORE_L_SHIFT, .smps_volra_mask = OMAP4430_VOLRA_VDD_CORE_L_MASK, + .smps_cmdra_mask = OMAP4430_CMDRA_VDD_CORE_L_MASK, + .cfg_channel_sa_shift = OMAP4430_SA_VDD_CORE_L_SHIFT, + .cfg_ch_bits = &vc44xx_common_cfg_channel, }; diff --git a/arch/arm/mach-omap2/voltage.c b/arch/arm/mach-omap2/voltage.c index 0c1552d9d995..a501597c3ea0 100644 --- a/arch/arm/mach-omap2/voltage.c +++ b/arch/arm/mach-omap2/voltage.c @@ -36,634 +36,51 @@ #include "control.h" #include "voltage.h" +#include "powerdomain.h" #include "vc.h" #include "vp.h" -#define VOLTAGE_DIR_SIZE 16 +static LIST_HEAD(voltdm_list); - -static struct omap_vdd_info **vdd_info; - -/* - * Number of scalable voltage domains. - */ -static int nr_scalable_vdd; - -/* XXX document */ -static s16 prm_mod_offs; -static s16 prm_irqst_ocp_mod_offs; - -static struct dentry *voltage_dir; - -/* Init function pointers */ -static int vp_forceupdate_scale_voltage(struct omap_vdd_info *vdd, - unsigned long target_volt); - -static u32 omap3_voltage_read_reg(u16 mod, u8 offset) -{ - return omap2_prm_read_mod_reg(mod, offset); -} - -static void omap3_voltage_write_reg(u32 val, u16 mod, u8 offset) +static int __init _config_common_vdd_data(struct voltagedomain *voltdm) { - omap2_prm_write_mod_reg(val, mod, offset); -} - -static u32 omap4_voltage_read_reg(u16 mod, u8 offset) -{ - return omap4_prminst_read_inst_reg(OMAP4430_PRM_PARTITION, - mod, offset); -} - -static void omap4_voltage_write_reg(u32 val, u16 mod, u8 offset) -{ - omap4_prminst_write_inst_reg(val, OMAP4430_PRM_PARTITION, mod, offset); -} - -static int __init _config_common_vdd_data(struct omap_vdd_info *vdd) -{ - char *sys_ck_name; struct clk *sys_ck; - u32 sys_clk_speed, timeout_val, waittime; - - /* - * XXX Clockfw should handle this, or this should be in a - * struct record - */ - if (cpu_is_omap24xx() || cpu_is_omap34xx()) - sys_ck_name = "sys_ck"; - else if (cpu_is_omap44xx()) - sys_ck_name = "sys_clkin_ck"; - else - return -EINVAL; /* * Sys clk rate is require to calculate vp timeout value and * smpswaittimemin and smpswaittimemax. */ - sys_ck = clk_get(NULL, sys_ck_name); + sys_ck = clk_get(NULL, voltdm->sys_clk.name); if (IS_ERR(sys_ck)) { pr_warning("%s: Could not get the sys clk to calculate" - "various vdd_%s params\n", __func__, vdd->voltdm.name); + "various vdd_%s params\n", __func__, voltdm->name); return -EINVAL; } - sys_clk_speed = clk_get_rate(sys_ck); - clk_put(sys_ck); - /* Divide to avoid overflow */ - sys_clk_speed /= 1000; + voltdm->sys_clk.rate = clk_get_rate(sys_ck); + WARN_ON(!voltdm->sys_clk.rate); /* Generic voltage parameters */ - vdd->volt_scale = vp_forceupdate_scale_voltage; - vdd->vp_enabled = false; - - vdd->vp_rt_data.vpconfig_erroroffset = - (vdd->pmic_info->vp_erroroffset << - vdd->vp_data->vp_common->vpconfig_erroroffset_shift); - - timeout_val = (sys_clk_speed * vdd->pmic_info->vp_timeout_us) / 1000; - vdd->vp_rt_data.vlimitto_timeout = timeout_val; - vdd->vp_rt_data.vlimitto_vddmin = vdd->pmic_info->vp_vddmin; - vdd->vp_rt_data.vlimitto_vddmax = vdd->pmic_info->vp_vddmax; - - waittime = ((vdd->pmic_info->step_size / vdd->pmic_info->slew_rate) * - sys_clk_speed) / 1000; - vdd->vp_rt_data.vstepmin_smpswaittimemin = waittime; - vdd->vp_rt_data.vstepmax_smpswaittimemax = waittime; - vdd->vp_rt_data.vstepmin_stepmin = vdd->pmic_info->vp_vstepmin; - vdd->vp_rt_data.vstepmax_stepmax = vdd->pmic_info->vp_vstepmax; - - return 0; -} - -/* Voltage debugfs support */ -static int vp_volt_debug_get(void *data, u64 *val) -{ - struct omap_vdd_info *vdd = (struct omap_vdd_info *) data; - u8 vsel; - - if (!vdd) { - pr_warning("Wrong paramater passed\n"); - return -EINVAL; - } - - vsel = vdd->read_reg(prm_mod_offs, vdd->vp_data->voltage); - pr_notice("curr_vsel = %x\n", vsel); - - if (!vdd->pmic_info->vsel_to_uv) { - pr_warning("PMIC function to convert vsel to voltage" - "in uV not registerd\n"); - return -EINVAL; - } - - *val = vdd->pmic_info->vsel_to_uv(vsel); - return 0; -} - -static int nom_volt_debug_get(void *data, u64 *val) -{ - struct omap_vdd_info *vdd = (struct omap_vdd_info *) data; - - if (!vdd) { - pr_warning("Wrong paramater passed\n"); - return -EINVAL; - } - - *val = omap_voltage_get_nom_volt(&vdd->voltdm); - - return 0; -} - -DEFINE_SIMPLE_ATTRIBUTE(vp_volt_debug_fops, vp_volt_debug_get, NULL, "%llu\n"); -DEFINE_SIMPLE_ATTRIBUTE(nom_volt_debug_fops, nom_volt_debug_get, NULL, - "%llu\n"); -static void vp_latch_vsel(struct omap_vdd_info *vdd) -{ - u32 vpconfig; - unsigned long uvdc; - char vsel; - - uvdc = omap_voltage_get_nom_volt(&vdd->voltdm); - if (!uvdc) { - pr_warning("%s: unable to find current voltage for vdd_%s\n", - __func__, vdd->voltdm.name); - return; - } - - if (!vdd->pmic_info || !vdd->pmic_info->uv_to_vsel) { - pr_warning("%s: PMIC function to convert voltage in uV to" - " vsel not registered\n", __func__); - return; - } - - vsel = vdd->pmic_info->uv_to_vsel(uvdc); - - vpconfig = vdd->read_reg(prm_mod_offs, vdd->vp_data->vpconfig); - vpconfig &= ~(vdd->vp_data->vp_common->vpconfig_initvoltage_mask | - vdd->vp_data->vp_common->vpconfig_initvdd); - vpconfig |= vsel << vdd->vp_data->vp_common->vpconfig_initvoltage_shift; - - vdd->write_reg(vpconfig, prm_mod_offs, vdd->vp_data->vpconfig); - - /* Trigger initVDD value copy to voltage processor */ - vdd->write_reg((vpconfig | vdd->vp_data->vp_common->vpconfig_initvdd), - prm_mod_offs, vdd->vp_data->vpconfig); - - /* Clear initVDD copy trigger bit */ - vdd->write_reg(vpconfig, prm_mod_offs, vdd->vp_data->vpconfig); -} - -/* Generic voltage init functions */ -static void __init vp_init(struct omap_vdd_info *vdd) -{ - u32 vp_val; - - if (!vdd->read_reg || !vdd->write_reg) { - pr_err("%s: No read/write API for accessing vdd_%s regs\n", - __func__, vdd->voltdm.name); - return; - } - - vp_val = vdd->vp_rt_data.vpconfig_erroroffset | - (vdd->vp_rt_data.vpconfig_errorgain << - vdd->vp_data->vp_common->vpconfig_errorgain_shift) | - vdd->vp_data->vp_common->vpconfig_timeouten; - vdd->write_reg(vp_val, prm_mod_offs, vdd->vp_data->vpconfig); - - vp_val = ((vdd->vp_rt_data.vstepmin_smpswaittimemin << - vdd->vp_data->vp_common->vstepmin_smpswaittimemin_shift) | - (vdd->vp_rt_data.vstepmin_stepmin << - vdd->vp_data->vp_common->vstepmin_stepmin_shift)); - vdd->write_reg(vp_val, prm_mod_offs, vdd->vp_data->vstepmin); - - vp_val = ((vdd->vp_rt_data.vstepmax_smpswaittimemax << - vdd->vp_data->vp_common->vstepmax_smpswaittimemax_shift) | - (vdd->vp_rt_data.vstepmax_stepmax << - vdd->vp_data->vp_common->vstepmax_stepmax_shift)); - vdd->write_reg(vp_val, prm_mod_offs, vdd->vp_data->vstepmax); - - vp_val = ((vdd->vp_rt_data.vlimitto_vddmax << - vdd->vp_data->vp_common->vlimitto_vddmax_shift) | - (vdd->vp_rt_data.vlimitto_vddmin << - vdd->vp_data->vp_common->vlimitto_vddmin_shift) | - (vdd->vp_rt_data.vlimitto_timeout << - vdd->vp_data->vp_common->vlimitto_timeout_shift)); - vdd->write_reg(vp_val, prm_mod_offs, vdd->vp_data->vlimitto); -} - -static void __init vdd_debugfs_init(struct omap_vdd_info *vdd) -{ - char *name; - - name = kzalloc(VOLTAGE_DIR_SIZE, GFP_KERNEL); - if (!name) { - pr_warning("%s: Unable to allocate memory for debugfs" - " directory name for vdd_%s", - __func__, vdd->voltdm.name); - return; - } - strcpy(name, "vdd_"); - strcat(name, vdd->voltdm.name); - - vdd->debug_dir = debugfs_create_dir(name, voltage_dir); - kfree(name); - if (IS_ERR(vdd->debug_dir)) { - pr_warning("%s: Unable to create debugfs directory for" - " vdd_%s\n", __func__, vdd->voltdm.name); - vdd->debug_dir = NULL; - return; - } - - (void) debugfs_create_x16("vp_errorgain", S_IRUGO, vdd->debug_dir, - &(vdd->vp_rt_data.vpconfig_errorgain)); - (void) debugfs_create_x16("vp_smpswaittimemin", S_IRUGO, - vdd->debug_dir, - &(vdd->vp_rt_data.vstepmin_smpswaittimemin)); - (void) debugfs_create_x8("vp_stepmin", S_IRUGO, vdd->debug_dir, - &(vdd->vp_rt_data.vstepmin_stepmin)); - (void) debugfs_create_x16("vp_smpswaittimemax", S_IRUGO, - vdd->debug_dir, - &(vdd->vp_rt_data.vstepmax_smpswaittimemax)); - (void) debugfs_create_x8("vp_stepmax", S_IRUGO, vdd->debug_dir, - &(vdd->vp_rt_data.vstepmax_stepmax)); - (void) debugfs_create_x8("vp_vddmax", S_IRUGO, vdd->debug_dir, - &(vdd->vp_rt_data.vlimitto_vddmax)); - (void) debugfs_create_x8("vp_vddmin", S_IRUGO, vdd->debug_dir, - &(vdd->vp_rt_data.vlimitto_vddmin)); - (void) debugfs_create_x16("vp_timeout", S_IRUGO, vdd->debug_dir, - &(vdd->vp_rt_data.vlimitto_timeout)); - (void) debugfs_create_file("curr_vp_volt", S_IRUGO, vdd->debug_dir, - (void *) vdd, &vp_volt_debug_fops); - (void) debugfs_create_file("curr_nominal_volt", S_IRUGO, - vdd->debug_dir, (void *) vdd, - &nom_volt_debug_fops); -} - -/* Voltage scale and accessory APIs */ -static int _pre_volt_scale(struct omap_vdd_info *vdd, - unsigned long target_volt, u8 *target_vsel, u8 *current_vsel) -{ - struct omap_volt_data *volt_data; - const struct omap_vc_common_data *vc_common; - const struct omap_vp_common_data *vp_common; - u32 vc_cmdval, vp_errgain_val; - - vc_common = vdd->vc_data->vc_common; - vp_common = vdd->vp_data->vp_common; - - /* Check if suffiecient pmic info is available for this vdd */ - if (!vdd->pmic_info) { - pr_err("%s: Insufficient pmic info to scale the vdd_%s\n", - __func__, vdd->voltdm.name); - return -EINVAL; - } + voltdm->scale = omap_vp_forceupdate_scale; - if (!vdd->pmic_info->uv_to_vsel) { - pr_err("%s: PMIC function to convert voltage in uV to" - "vsel not registered. Hence unable to scale voltage" - "for vdd_%s\n", __func__, vdd->voltdm.name); - return -ENODATA; - } - - if (!vdd->read_reg || !vdd->write_reg) { - pr_err("%s: No read/write API for accessing vdd_%s regs\n", - __func__, vdd->voltdm.name); - return -EINVAL; - } - - /* Get volt_data corresponding to target_volt */ - volt_data = omap_voltage_get_voltdata(&vdd->voltdm, target_volt); - if (IS_ERR(volt_data)) - volt_data = NULL; - - *target_vsel = vdd->pmic_info->uv_to_vsel(target_volt); - *current_vsel = vdd->read_reg(prm_mod_offs, vdd->vp_data->voltage); - - /* Setting the ON voltage to the new target voltage */ - vc_cmdval = vdd->read_reg(prm_mod_offs, vdd->vc_data->cmdval_reg); - vc_cmdval &= ~vc_common->cmd_on_mask; - vc_cmdval |= (*target_vsel << vc_common->cmd_on_shift); - vdd->write_reg(vc_cmdval, prm_mod_offs, vdd->vc_data->cmdval_reg); - - /* Setting vp errorgain based on the voltage */ - if (volt_data) { - vp_errgain_val = vdd->read_reg(prm_mod_offs, - vdd->vp_data->vpconfig); - vdd->vp_rt_data.vpconfig_errorgain = volt_data->vp_errgain; - vp_errgain_val &= ~vp_common->vpconfig_errorgain_mask; - vp_errgain_val |= vdd->vp_rt_data.vpconfig_errorgain << - vp_common->vpconfig_errorgain_shift; - vdd->write_reg(vp_errgain_val, prm_mod_offs, - vdd->vp_data->vpconfig); - } - - return 0; -} - -static void _post_volt_scale(struct omap_vdd_info *vdd, - unsigned long target_volt, u8 target_vsel, u8 current_vsel) -{ - u32 smps_steps = 0, smps_delay = 0; - - smps_steps = abs(target_vsel - current_vsel); - /* SMPS slew rate / step size. 2us added as buffer. */ - smps_delay = ((smps_steps * vdd->pmic_info->step_size) / - vdd->pmic_info->slew_rate) + 2; - udelay(smps_delay); - - vdd->curr_volt = target_volt; -} - -/* vc_bypass_scale_voltage - VC bypass method of voltage scaling */ -static int vc_bypass_scale_voltage(struct omap_vdd_info *vdd, - unsigned long target_volt) -{ - u32 loop_cnt = 0, retries_cnt = 0; - u32 vc_valid, vc_bypass_val_reg, vc_bypass_value; - u8 target_vsel, current_vsel; - int ret; - - ret = _pre_volt_scale(vdd, target_volt, &target_vsel, ¤t_vsel); - if (ret) - return ret; - - vc_valid = vdd->vc_data->vc_common->valid; - vc_bypass_val_reg = vdd->vc_data->vc_common->bypass_val_reg; - vc_bypass_value = (target_vsel << vdd->vc_data->vc_common->data_shift) | - (vdd->pmic_info->pmic_reg << - vdd->vc_data->vc_common->regaddr_shift) | - (vdd->pmic_info->i2c_slave_addr << - vdd->vc_data->vc_common->slaveaddr_shift); - - vdd->write_reg(vc_bypass_value, prm_mod_offs, vc_bypass_val_reg); - vdd->write_reg(vc_bypass_value | vc_valid, prm_mod_offs, - vc_bypass_val_reg); - - vc_bypass_value = vdd->read_reg(prm_mod_offs, vc_bypass_val_reg); - /* - * Loop till the bypass command is acknowledged from the SMPS. - * NOTE: This is legacy code. The loop count and retry count needs - * to be revisited. - */ - while (!(vc_bypass_value & vc_valid)) { - loop_cnt++; - - if (retries_cnt > 10) { - pr_warning("%s: Retry count exceeded\n", __func__); - return -ETIMEDOUT; - } - - if (loop_cnt > 50) { - retries_cnt++; - loop_cnt = 0; - udelay(10); - } - vc_bypass_value = vdd->read_reg(prm_mod_offs, - vc_bypass_val_reg); - } - - _post_volt_scale(vdd, target_volt, target_vsel, current_vsel); return 0; } -/* VP force update method of voltage scaling */ -static int vp_forceupdate_scale_voltage(struct omap_vdd_info *vdd, - unsigned long target_volt) -{ - u32 vpconfig; - u8 target_vsel, current_vsel, prm_irqst_reg; - int ret, timeout = 0; - - ret = _pre_volt_scale(vdd, target_volt, &target_vsel, ¤t_vsel); - if (ret) - return ret; - - prm_irqst_reg = vdd->vp_data->prm_irqst_data->prm_irqst_reg; - - /* - * Clear all pending TransactionDone interrupt/status. Typical latency - * is <3us - */ - while (timeout++ < VP_TRANXDONE_TIMEOUT) { - vdd->write_reg(vdd->vp_data->prm_irqst_data->tranxdone_status, - prm_irqst_ocp_mod_offs, prm_irqst_reg); - if (!(vdd->read_reg(prm_irqst_ocp_mod_offs, prm_irqst_reg) & - vdd->vp_data->prm_irqst_data->tranxdone_status)) - break; - udelay(1); - } - if (timeout >= VP_TRANXDONE_TIMEOUT) { - pr_warning("%s: vdd_%s TRANXDONE timeout exceeded." - "Voltage change aborted", __func__, vdd->voltdm.name); - return -ETIMEDOUT; - } - - /* Configure for VP-Force Update */ - vpconfig = vdd->read_reg(prm_mod_offs, vdd->vp_data->vpconfig); - vpconfig &= ~(vdd->vp_data->vp_common->vpconfig_initvdd | - vdd->vp_data->vp_common->vpconfig_forceupdate | - vdd->vp_data->vp_common->vpconfig_initvoltage_mask); - vpconfig |= ((target_vsel << - vdd->vp_data->vp_common->vpconfig_initvoltage_shift)); - vdd->write_reg(vpconfig, prm_mod_offs, vdd->vp_data->vpconfig); - - /* Trigger initVDD value copy to voltage processor */ - vpconfig |= vdd->vp_data->vp_common->vpconfig_initvdd; - vdd->write_reg(vpconfig, prm_mod_offs, vdd->vp_data->vpconfig); - - /* Force update of voltage */ - vpconfig |= vdd->vp_data->vp_common->vpconfig_forceupdate; - vdd->write_reg(vpconfig, prm_mod_offs, vdd->vp_data->vpconfig); - - /* - * Wait for TransactionDone. Typical latency is <200us. - * Depends on SMPSWAITTIMEMIN/MAX and voltage change - */ - timeout = 0; - omap_test_timeout((vdd->read_reg(prm_irqst_ocp_mod_offs, prm_irqst_reg) & - vdd->vp_data->prm_irqst_data->tranxdone_status), - VP_TRANXDONE_TIMEOUT, timeout); - if (timeout >= VP_TRANXDONE_TIMEOUT) - pr_err("%s: vdd_%s TRANXDONE timeout exceeded." - "TRANXDONE never got set after the voltage update\n", - __func__, vdd->voltdm.name); - - _post_volt_scale(vdd, target_volt, target_vsel, current_vsel); - - /* - * Disable TransactionDone interrupt , clear all status, clear - * control registers - */ - timeout = 0; - while (timeout++ < VP_TRANXDONE_TIMEOUT) { - vdd->write_reg(vdd->vp_data->prm_irqst_data->tranxdone_status, - prm_irqst_ocp_mod_offs, prm_irqst_reg); - if (!(vdd->read_reg(prm_irqst_ocp_mod_offs, prm_irqst_reg) & - vdd->vp_data->prm_irqst_data->tranxdone_status)) - break; - udelay(1); - } - - if (timeout >= VP_TRANXDONE_TIMEOUT) - pr_warning("%s: vdd_%s TRANXDONE timeout exceeded while trying" - "to clear the TRANXDONE status\n", - __func__, vdd->voltdm.name); - - vpconfig = vdd->read_reg(prm_mod_offs, vdd->vp_data->vpconfig); - /* Clear initVDD copy trigger bit */ - vpconfig &= ~vdd->vp_data->vp_common->vpconfig_initvdd; - vdd->write_reg(vpconfig, prm_mod_offs, vdd->vp_data->vpconfig); - /* Clear force bit */ - vpconfig &= ~vdd->vp_data->vp_common->vpconfig_forceupdate; - vdd->write_reg(vpconfig, prm_mod_offs, vdd->vp_data->vpconfig); - - return 0; -} - -static void __init omap3_vfsm_init(struct omap_vdd_info *vdd) -{ - /* - * Voltage Manager FSM parameters init - * XXX This data should be passed in from the board file - */ - vdd->write_reg(OMAP3_CLKSETUP, prm_mod_offs, OMAP3_PRM_CLKSETUP_OFFSET); - vdd->write_reg(OMAP3_VOLTOFFSET, prm_mod_offs, - OMAP3_PRM_VOLTOFFSET_OFFSET); - vdd->write_reg(OMAP3_VOLTSETUP2, prm_mod_offs, - OMAP3_PRM_VOLTSETUP2_OFFSET); -} - -static void __init omap3_vc_init(struct omap_vdd_info *vdd) -{ - static bool is_initialized; - u8 on_vsel, onlp_vsel, ret_vsel, off_vsel; - u32 vc_val; - - if (is_initialized) - return; - - /* Set up the on, inactive, retention and off voltage */ - on_vsel = vdd->pmic_info->uv_to_vsel(vdd->pmic_info->on_volt); - onlp_vsel = vdd->pmic_info->uv_to_vsel(vdd->pmic_info->onlp_volt); - ret_vsel = vdd->pmic_info->uv_to_vsel(vdd->pmic_info->ret_volt); - off_vsel = vdd->pmic_info->uv_to_vsel(vdd->pmic_info->off_volt); - vc_val = ((on_vsel << vdd->vc_data->vc_common->cmd_on_shift) | - (onlp_vsel << vdd->vc_data->vc_common->cmd_onlp_shift) | - (ret_vsel << vdd->vc_data->vc_common->cmd_ret_shift) | - (off_vsel << vdd->vc_data->vc_common->cmd_off_shift)); - vdd->write_reg(vc_val, prm_mod_offs, vdd->vc_data->cmdval_reg); - - /* - * Generic VC parameters init - * XXX This data should be abstracted out - */ - vdd->write_reg(OMAP3430_CMD1_MASK | OMAP3430_RAV1_MASK, prm_mod_offs, - OMAP3_PRM_VC_CH_CONF_OFFSET); - vdd->write_reg(OMAP3430_MCODE_SHIFT | OMAP3430_HSEN_MASK, prm_mod_offs, - OMAP3_PRM_VC_I2C_CFG_OFFSET); - - omap3_vfsm_init(vdd); - - is_initialized = true; -} - - -/* OMAP4 specific voltage init functions */ -static void __init omap4_vc_init(struct omap_vdd_info *vdd) -{ - static bool is_initialized; - u32 vc_val; - - if (is_initialized) - return; - - /* TODO: Configure setup times and CMD_VAL values*/ - - /* - * Generic VC parameters init - * XXX This data should be abstracted out - */ - vc_val = (OMAP4430_RAV_VDD_MPU_L_MASK | OMAP4430_CMD_VDD_MPU_L_MASK | - OMAP4430_RAV_VDD_IVA_L_MASK | OMAP4430_CMD_VDD_IVA_L_MASK | - OMAP4430_RAV_VDD_CORE_L_MASK | OMAP4430_CMD_VDD_CORE_L_MASK); - vdd->write_reg(vc_val, prm_mod_offs, OMAP4_PRM_VC_CFG_CHANNEL_OFFSET); - - /* XXX These are magic numbers and do not belong! */ - vc_val = (0x60 << OMAP4430_SCLL_SHIFT | 0x26 << OMAP4430_SCLH_SHIFT); - vdd->write_reg(vc_val, prm_mod_offs, OMAP4_PRM_VC_CFG_I2C_CLK_OFFSET); - - is_initialized = true; -} - -static void __init omap_vc_init(struct omap_vdd_info *vdd) -{ - u32 vc_val; - - if (!vdd->pmic_info || !vdd->pmic_info->uv_to_vsel) { - pr_err("%s: PMIC info requried to configure vc for" - "vdd_%s not populated.Hence cannot initialize vc\n", - __func__, vdd->voltdm.name); - return; - } - - if (!vdd->read_reg || !vdd->write_reg) { - pr_err("%s: No read/write API for accessing vdd_%s regs\n", - __func__, vdd->voltdm.name); - return; - } - - /* Set up the SMPS_SA(i2c slave address in VC */ - vc_val = vdd->read_reg(prm_mod_offs, - vdd->vc_data->vc_common->smps_sa_reg); - vc_val &= ~vdd->vc_data->smps_sa_mask; - vc_val |= vdd->pmic_info->i2c_slave_addr << vdd->vc_data->smps_sa_shift; - vdd->write_reg(vc_val, prm_mod_offs, - vdd->vc_data->vc_common->smps_sa_reg); - - /* Setup the VOLRA(pmic reg addr) in VC */ - vc_val = vdd->read_reg(prm_mod_offs, - vdd->vc_data->vc_common->smps_volra_reg); - vc_val &= ~vdd->vc_data->smps_volra_mask; - vc_val |= vdd->pmic_info->pmic_reg << vdd->vc_data->smps_volra_shift; - vdd->write_reg(vc_val, prm_mod_offs, - vdd->vc_data->vc_common->smps_volra_reg); - - /* Configure the setup times */ - vc_val = vdd->read_reg(prm_mod_offs, vdd->vfsm->voltsetup_reg); - vc_val &= ~vdd->vfsm->voltsetup_mask; - vc_val |= vdd->pmic_info->volt_setup_time << - vdd->vfsm->voltsetup_shift; - vdd->write_reg(vc_val, prm_mod_offs, vdd->vfsm->voltsetup_reg); - - if (cpu_is_omap34xx()) - omap3_vc_init(vdd); - else if (cpu_is_omap44xx()) - omap4_vc_init(vdd); -} - -static int __init omap_vdd_data_configure(struct omap_vdd_info *vdd) +static int __init omap_vdd_data_configure(struct voltagedomain *voltdm) { int ret = -EINVAL; - if (!vdd->pmic_info) { + if (!voltdm->pmic) { pr_err("%s: PMIC info requried to configure vdd_%s not" "populated.Hence cannot initialize vdd_%s\n", - __func__, vdd->voltdm.name, vdd->voltdm.name); + __func__, voltdm->name, voltdm->name); goto ovdc_out; } - if (IS_ERR_VALUE(_config_common_vdd_data(vdd))) + if (IS_ERR_VALUE(_config_common_vdd_data(voltdm))) goto ovdc_out; - if (cpu_is_omap34xx()) { - vdd->read_reg = omap3_voltage_read_reg; - vdd->write_reg = omap3_voltage_write_reg; - ret = 0; - } else if (cpu_is_omap44xx()) { - vdd->read_reg = omap4_voltage_read_reg; - vdd->write_reg = omap4_voltage_write_reg; - ret = 0; - } + ret = 0; ovdc_out: return ret; @@ -679,197 +96,65 @@ ovdc_out: */ unsigned long omap_voltage_get_nom_volt(struct voltagedomain *voltdm) { - struct omap_vdd_info *vdd; - - if (!voltdm || IS_ERR(voltdm)) { - pr_warning("%s: VDD specified does not exist!\n", __func__); - return 0; - } - - vdd = container_of(voltdm, struct omap_vdd_info, voltdm); - - return vdd->curr_volt; -} - -/** - * omap_vp_get_curr_volt() - API to get the current vp voltage. - * @voltdm: pointer to the VDD. - * - * This API returns the current voltage for the specified voltage processor - */ -unsigned long omap_vp_get_curr_volt(struct voltagedomain *voltdm) -{ - struct omap_vdd_info *vdd; - u8 curr_vsel; - if (!voltdm || IS_ERR(voltdm)) { pr_warning("%s: VDD specified does not exist!\n", __func__); return 0; } - vdd = container_of(voltdm, struct omap_vdd_info, voltdm); - if (!vdd->read_reg) { - pr_err("%s: No read API for reading vdd_%s regs\n", - __func__, voltdm->name); - return 0; - } - - curr_vsel = vdd->read_reg(prm_mod_offs, vdd->vp_data->voltage); - - if (!vdd->pmic_info || !vdd->pmic_info->vsel_to_uv) { - pr_warning("%s: PMIC function to convert vsel to voltage" - "in uV not registerd\n", __func__); - return 0; - } - - return vdd->pmic_info->vsel_to_uv(curr_vsel); -} - -/** - * omap_vp_enable() - API to enable a particular VP - * @voltdm: pointer to the VDD whose VP is to be enabled. - * - * This API enables a particular voltage processor. Needed by the smartreflex - * class drivers. - */ -void omap_vp_enable(struct voltagedomain *voltdm) -{ - struct omap_vdd_info *vdd; - u32 vpconfig; - - if (!voltdm || IS_ERR(voltdm)) { - pr_warning("%s: VDD specified does not exist!\n", __func__); - return; - } - - vdd = container_of(voltdm, struct omap_vdd_info, voltdm); - if (!vdd->read_reg || !vdd->write_reg) { - pr_err("%s: No read/write API for accessing vdd_%s regs\n", - __func__, voltdm->name); - return; - } - - /* If VP is already enabled, do nothing. Return */ - if (vdd->vp_enabled) - return; - - vp_latch_vsel(vdd); - - /* Enable VP */ - vpconfig = vdd->read_reg(prm_mod_offs, vdd->vp_data->vpconfig); - vpconfig |= vdd->vp_data->vp_common->vpconfig_vpenable; - vdd->write_reg(vpconfig, prm_mod_offs, vdd->vp_data->vpconfig); - vdd->vp_enabled = true; + return voltdm->curr_volt; } /** - * omap_vp_disable() - API to disable a particular VP - * @voltdm: pointer to the VDD whose VP is to be disabled. - * - * This API disables a particular voltage processor. Needed by the smartreflex - * class drivers. - */ -void omap_vp_disable(struct voltagedomain *voltdm) -{ - struct omap_vdd_info *vdd; - u32 vpconfig; - int timeout; - - if (!voltdm || IS_ERR(voltdm)) { - pr_warning("%s: VDD specified does not exist!\n", __func__); - return; - } - - vdd = container_of(voltdm, struct omap_vdd_info, voltdm); - if (!vdd->read_reg || !vdd->write_reg) { - pr_err("%s: No read/write API for accessing vdd_%s regs\n", - __func__, voltdm->name); - return; - } - - /* If VP is already disabled, do nothing. Return */ - if (!vdd->vp_enabled) { - pr_warning("%s: Trying to disable VP for vdd_%s when" - "it is already disabled\n", __func__, voltdm->name); - return; - } - - /* Disable VP */ - vpconfig = vdd->read_reg(prm_mod_offs, vdd->vp_data->vpconfig); - vpconfig &= ~vdd->vp_data->vp_common->vpconfig_vpenable; - vdd->write_reg(vpconfig, prm_mod_offs, vdd->vp_data->vpconfig); - - /* - * Wait for VP idle Typical latency is <2us. Maximum latency is ~100us - */ - omap_test_timeout((vdd->read_reg(prm_mod_offs, vdd->vp_data->vstatus)), - VP_IDLE_TIMEOUT, timeout); - - if (timeout >= VP_IDLE_TIMEOUT) - pr_warning("%s: vdd_%s idle timedout\n", - __func__, voltdm->name); - - vdd->vp_enabled = false; - - return; -} - -/** - * omap_voltage_scale_vdd() - API to scale voltage of a particular - * voltage domain. - * @voltdm: pointer to the VDD which is to be scaled. - * @target_volt: The target voltage of the voltage domain + * voltdm_scale() - API to scale voltage of a particular voltage domain. + * @voltdm: pointer to the voltage domain which is to be scaled. + * @target_volt: The target voltage of the voltage domain * * This API should be called by the kernel to do the voltage scaling - * for a particular voltage domain during dvfs or any other situation. + * for a particular voltage domain during DVFS. */ -int omap_voltage_scale_vdd(struct voltagedomain *voltdm, - unsigned long target_volt) +int voltdm_scale(struct voltagedomain *voltdm, + unsigned long target_volt) { - struct omap_vdd_info *vdd; - if (!voltdm || IS_ERR(voltdm)) { pr_warning("%s: VDD specified does not exist!\n", __func__); return -EINVAL; } - vdd = container_of(voltdm, struct omap_vdd_info, voltdm); - - if (!vdd->volt_scale) { + if (!voltdm->scale) { pr_err("%s: No voltage scale API registered for vdd_%s\n", __func__, voltdm->name); return -ENODATA; } - return vdd->volt_scale(vdd, target_volt); + return voltdm->scale(voltdm, target_volt); } /** - * omap_voltage_reset() - Resets the voltage of a particular voltage domain - * to that of the current OPP. - * @voltdm: pointer to the VDD whose voltage is to be reset. + * voltdm_reset() - Resets the voltage of a particular voltage domain + * to that of the current OPP. + * @voltdm: pointer to the voltage domain whose voltage is to be reset. * * This API finds out the correct voltage the voltage domain is supposed * to be at and resets the voltage to that level. Should be used especially * while disabling any voltage compensation modules. */ -void omap_voltage_reset(struct voltagedomain *voltdm) +void voltdm_reset(struct voltagedomain *voltdm) { - unsigned long target_uvdc; + unsigned long target_volt; if (!voltdm || IS_ERR(voltdm)) { pr_warning("%s: VDD specified does not exist!\n", __func__); return; } - target_uvdc = omap_voltage_get_nom_volt(voltdm); - if (!target_uvdc) { + target_volt = omap_voltage_get_nom_volt(voltdm); + if (!target_volt) { pr_err("%s: unable to find current voltage for vdd_%s\n", __func__, voltdm->name); return; } - omap_voltage_scale_vdd(voltdm, target_uvdc); + voltdm_scale(voltdm, target_volt); } /** @@ -894,7 +179,7 @@ void omap_voltage_get_volttable(struct voltagedomain *voltdm, return; } - vdd = container_of(voltdm, struct omap_vdd_info, voltdm); + vdd = voltdm->vdd; *volt_data = vdd->volt_data; } @@ -925,7 +210,7 @@ struct omap_volt_data *omap_voltage_get_voltdata(struct voltagedomain *voltdm, return ERR_PTR(-EINVAL); } - vdd = container_of(voltdm, struct omap_vdd_info, voltdm); + vdd = voltdm->vdd; if (!vdd->volt_data) { pr_warning("%s: voltage table does not exist for vdd_%s\n", @@ -948,54 +233,25 @@ struct omap_volt_data *omap_voltage_get_voltdata(struct voltagedomain *voltdm, * omap_voltage_register_pmic() - API to register PMIC specific data * @voltdm: pointer to the VDD for which the PMIC specific data is * to be registered - * @pmic_info: the structure containing pmic info + * @pmic: the structure containing pmic info * * This API is to be called by the SOC/PMIC file to specify the - * pmic specific info as present in omap_volt_pmic_info structure. + * pmic specific info as present in omap_voltdm_pmic structure. */ int omap_voltage_register_pmic(struct voltagedomain *voltdm, - struct omap_volt_pmic_info *pmic_info) + struct omap_voltdm_pmic *pmic) { - struct omap_vdd_info *vdd; - if (!voltdm || IS_ERR(voltdm)) { pr_warning("%s: VDD specified does not exist!\n", __func__); return -EINVAL; } - vdd = container_of(voltdm, struct omap_vdd_info, voltdm); - - vdd->pmic_info = pmic_info; + voltdm->pmic = pmic; return 0; } /** - * omap_voltage_get_dbgdir() - API to get pointer to the debugfs directory - * corresponding to a voltage domain. - * - * @voltdm: pointer to the VDD whose debug directory is required. - * - * This API returns pointer to the debugfs directory corresponding - * to the voltage domain. Should be used by drivers requiring to - * add any debug entry for a particular voltage domain. Returns NULL - * in case of error. - */ -struct dentry *omap_voltage_get_dbgdir(struct voltagedomain *voltdm) -{ - struct omap_vdd_info *vdd; - - if (!voltdm || IS_ERR(voltdm)) { - pr_warning("%s: VDD specified does not exist!\n", __func__); - return NULL; - } - - vdd = container_of(voltdm, struct omap_vdd_info, voltdm); - - return vdd->debug_dir; -} - -/** * omap_change_voltscale_method() - API to change the voltage scaling method. * @voltdm: pointer to the VDD whose voltage scaling method * has to be changed. @@ -1006,23 +262,19 @@ struct dentry *omap_voltage_get_dbgdir(struct voltagedomain *voltdm) * defined in voltage.h */ void omap_change_voltscale_method(struct voltagedomain *voltdm, - int voltscale_method) + int voltscale_method) { - struct omap_vdd_info *vdd; - if (!voltdm || IS_ERR(voltdm)) { pr_warning("%s: VDD specified does not exist!\n", __func__); return; } - vdd = container_of(voltdm, struct omap_vdd_info, voltdm); - switch (voltscale_method) { case VOLTSCALE_VPFORCEUPDATE: - vdd->volt_scale = vp_forceupdate_scale_voltage; + voltdm->scale = omap_vp_forceupdate_scale; return; case VOLTSCALE_VCBYPASS: - vdd->volt_scale = vc_bypass_scale_voltage; + voltdm->scale = omap_vc_bypass_scale_voltage; return; default: pr_warning("%s: Trying to change the method of voltage scaling" @@ -1030,36 +282,63 @@ void omap_change_voltscale_method(struct voltagedomain *voltdm, } } -/** - * omap_voltage_domain_lookup() - API to get the voltage domain pointer - * @name: Name of the voltage domain - * - * This API looks up in the global vdd_info struct for the - * existence of voltage domain <name>. If it exists, the API returns - * a pointer to the voltage domain structure corresponding to the - * VDD<name>. Else retuns error pointer. - */ -struct voltagedomain *omap_voltage_domain_lookup(char *name) +/* Voltage debugfs support */ +static int vp_volt_debug_get(void *data, u64 *val) { - int i; + struct voltagedomain *voltdm = (struct voltagedomain *)data; - if (!vdd_info) { - pr_err("%s: Voltage driver init not yet happened.Faulting!\n", - __func__); - return ERR_PTR(-EINVAL); + if (!voltdm) { + pr_warning("Wrong paramater passed\n"); + return -EINVAL; + } + *val = omap_vp_get_curr_volt(voltdm); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(vp_volt_debug_fops, vp_volt_debug_get, NULL, "%llu\n"); + +static int nom_volt_debug_get(void *data, u64 *val) +{ + struct voltagedomain *voltdm = (struct voltagedomain *) data; + + if (!voltdm) { + pr_warning("Wrong paramater passed\n"); + return -EINVAL; } + *val = omap_voltage_get_nom_volt(voltdm); + + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(nom_volt_debug_fops, nom_volt_debug_get, NULL, + "%llu\n"); + +static void __init voltdm_debugfs_init(struct dentry *voltage_dir, + struct voltagedomain *voltdm) +{ + char *name; + + name = kasprintf(GFP_KERNEL, "vdd_%s", voltdm->name); if (!name) { - pr_err("%s: No name to get the votage domain!\n", __func__); - return ERR_PTR(-EINVAL); + pr_warning("%s:vdd_%s: no mem for debugfs\n", __func__, + voltdm->name); + return; } - for (i = 0; i < nr_scalable_vdd; i++) { - if (!(strcmp(name, vdd_info[i]->voltdm.name))) - return &vdd_info[i]->voltdm; + voltdm->debug_dir = debugfs_create_dir(name, voltage_dir); + kfree(name); + if (IS_ERR_OR_NULL(voltdm->debug_dir)) { + pr_warning("%s: Unable to create debugfs directory for" + " vdd_%s\n", __func__, voltdm->name); + voltdm->debug_dir = NULL; + return; } - return ERR_PTR(-EINVAL); + (void) debugfs_create_file("curr_vp_volt", S_IRUGO, voltdm->debug_dir, + (void *) voltdm, &vp_volt_debug_fops); + (void) debugfs_create_file("curr_nominal_volt", S_IRUGO, + voltdm->debug_dir, (void *) voltdm, + &nom_volt_debug_fops); } /** @@ -1071,37 +350,179 @@ struct voltagedomain *omap_voltage_domain_lookup(char *name) */ int __init omap_voltage_late_init(void) { - int i; + struct voltagedomain *voltdm; + struct dentry *voltage_dir; - if (!vdd_info) { + if (list_empty(&voltdm_list)) { pr_err("%s: Voltage driver support not added\n", __func__); return -EINVAL; } voltage_dir = debugfs_create_dir("voltage", NULL); - if (IS_ERR(voltage_dir)) - pr_err("%s: Unable to create voltage debugfs main dir\n", - __func__); - for (i = 0; i < nr_scalable_vdd; i++) { - if (omap_vdd_data_configure(vdd_info[i])) + + list_for_each_entry(voltdm, &voltdm_list, node) { + if (!voltdm->scalable) continue; - omap_vc_init(vdd_info[i]); - vp_init(vdd_info[i]); - vdd_debugfs_init(vdd_info[i]); + + if (voltdm->vdd) { + if (omap_vdd_data_configure(voltdm)) + continue; + omap_vp_init(voltdm); + } + + if (voltdm->vc) + omap_vc_init_channel(voltdm); + + if (voltage_dir) + voltdm_debugfs_init(voltage_dir, voltdm); + } return 0; } -/* XXX document */ -int __init omap_voltage_early_init(s16 prm_mod, s16 prm_irqst_ocp_mod, - struct omap_vdd_info *omap_vdd_array[], - u8 omap_vdd_count) +static struct voltagedomain *_voltdm_lookup(const char *name) { - prm_mod_offs = prm_mod; - prm_irqst_ocp_mod_offs = prm_irqst_ocp_mod; - vdd_info = omap_vdd_array; - nr_scalable_vdd = omap_vdd_count; + struct voltagedomain *voltdm, *temp_voltdm; + + voltdm = NULL; + + list_for_each_entry(temp_voltdm, &voltdm_list, node) { + if (!strcmp(name, temp_voltdm->name)) { + voltdm = temp_voltdm; + break; + } + } + + return voltdm; +} + +/** + * voltdm_add_pwrdm - add a powerdomain to a voltagedomain + * @voltdm: struct voltagedomain * to add the powerdomain to + * @pwrdm: struct powerdomain * to associate with a voltagedomain + * + * Associate the powerdomain @pwrdm with a voltagedomain @voltdm. This + * enables the use of voltdm_for_each_pwrdm(). Returns -EINVAL if + * presented with invalid pointers; -ENOMEM if memory could not be allocated; + * or 0 upon success. + */ +int voltdm_add_pwrdm(struct voltagedomain *voltdm, struct powerdomain *pwrdm) +{ + if (!voltdm || !pwrdm) + return -EINVAL; + + pr_debug("voltagedomain: associating powerdomain %s with voltagedomain " + "%s\n", pwrdm->name, voltdm->name); + + list_add(&pwrdm->voltdm_node, &voltdm->pwrdm_list); + return 0; } + +/** + * voltdm_for_each_pwrdm - call function for each pwrdm in a voltdm + * @voltdm: struct voltagedomain * to iterate over + * @fn: callback function * + * + * Call the supplied function @fn for each powerdomain in the + * voltagedomain @voltdm. Returns -EINVAL if presented with invalid + * pointers; or passes along the last return value of the callback + * function, which should be 0 for success or anything else to + * indicate failure. + */ +int voltdm_for_each_pwrdm(struct voltagedomain *voltdm, + int (*fn)(struct voltagedomain *voltdm, + struct powerdomain *pwrdm)) +{ + struct powerdomain *pwrdm; + int ret = 0; + + if (!fn) + return -EINVAL; + + list_for_each_entry(pwrdm, &voltdm->pwrdm_list, voltdm_node) + ret = (*fn)(voltdm, pwrdm); + + return ret; +} + +/** + * voltdm_for_each - call function on each registered voltagedomain + * @fn: callback function * + * + * Call the supplied function @fn for each registered voltagedomain. + * The callback function @fn can return anything but 0 to bail out + * early from the iterator. Returns the last return value of the + * callback function, which should be 0 for success or anything else + * to indicate failure; or -EINVAL if the function pointer is null. + */ +int voltdm_for_each(int (*fn)(struct voltagedomain *voltdm, void *user), + void *user) +{ + struct voltagedomain *temp_voltdm; + int ret = 0; + + if (!fn) + return -EINVAL; + + list_for_each_entry(temp_voltdm, &voltdm_list, node) { + ret = (*fn)(temp_voltdm, user); + if (ret) + break; + } + + return ret; +} + +static int _voltdm_register(struct voltagedomain *voltdm) +{ + if (!voltdm || !voltdm->name) + return -EINVAL; + + INIT_LIST_HEAD(&voltdm->pwrdm_list); + list_add(&voltdm->node, &voltdm_list); + + pr_debug("voltagedomain: registered %s\n", voltdm->name); + + return 0; +} + +/** + * voltdm_lookup - look up a voltagedomain by name, return a pointer + * @name: name of voltagedomain + * + * Find a registered voltagedomain by its name @name. Returns a pointer + * to the struct voltagedomain if found, or NULL otherwise. + */ +struct voltagedomain *voltdm_lookup(const char *name) +{ + struct voltagedomain *voltdm ; + + if (!name) + return NULL; + + voltdm = _voltdm_lookup(name); + + return voltdm; +} + +/** + * voltdm_init - set up the voltagedomain layer + * @voltdm_list: array of struct voltagedomain pointers to register + * + * Loop through the array of voltagedomains @voltdm_list, registering all + * that are available on the current CPU. If voltdm_list is supplied + * and not null, all of the referenced voltagedomains will be + * registered. No return value. + */ +void voltdm_init(struct voltagedomain **voltdms) +{ + struct voltagedomain **v; + + if (voltdms) { + for (v = voltdms; *v; v++) + _voltdm_register(*v); + } +} diff --git a/arch/arm/mach-omap2/voltage.h b/arch/arm/mach-omap2/voltage.h index e9f5408244e0..7e1113eaf046 100644 --- a/arch/arm/mach-omap2/voltage.h +++ b/arch/arm/mach-omap2/voltage.h @@ -19,6 +19,8 @@ #include "vc.h" #include "vp.h" +struct powerdomain; + /* XXX document */ #define VOLTSCALE_VPFORCEUPDATE 1 #define VOLTSCALE_VCBYPASS 2 @@ -31,8 +33,10 @@ #define OMAP3_VOLTOFFSET 0xff #define OMAP3_VOLTSETUP2 0xff +struct omap_vdd_info; + /** - * struct omap_vfsm_instance_data - per-voltage manager FSM register/bitfield + * struct omap_vfsm_instance - per-voltage manager FSM register/bitfield * data * @voltsetup_mask: SETUP_TIME* bitmask in the PRM_VOLTSETUP* register * @voltsetup_reg: register offset of PRM_VOLTSETUP from PRM base @@ -42,7 +46,7 @@ * XXX It is not necessary to have both a _mask and a _shift for the same * bitfield - remove one! */ -struct omap_vfsm_instance_data { +struct omap_vfsm_instance { u32 voltsetup_mask; u8 voltsetup_reg; u8 voltsetup_shift; @@ -50,11 +54,41 @@ struct omap_vfsm_instance_data { /** * struct voltagedomain - omap voltage domain global structure. - * @name: Name of the voltage domain which can be used as a unique - * identifier. + * @name: Name of the voltage domain which can be used as a unique identifier. + * @scalable: Whether or not this voltage domain is scalable + * @node: list_head linking all voltage domains + * @pwrdm_node: list_head linking all powerdomains in this voltagedomain + * @vdd: to be removed + * @pwrdms: powerdomains in this voltagedomain + * @scale: function used to scale the voltage of the voltagedomain + * @curr_volt: current nominal voltage for this voltage domain */ struct voltagedomain { char *name; + bool scalable; + struct list_head node; + struct list_head pwrdm_list; + struct omap_vc_channel *vc; + const struct omap_vfsm_instance *vfsm; + struct omap_vp_instance *vp; + struct omap_voltdm_pmic *pmic; + + /* VC/VP register access functions: SoC specific */ + u32 (*read) (u8 offset); + void (*write) (u32 val, u8 offset); + u32 (*rmw)(u32 mask, u32 bits, u8 offset); + + union { + const char *name; + u32 rate; + } sys_clk; + + int (*scale) (struct voltagedomain *voltdm, + unsigned long target_volt); + u32 curr_volt; + + struct omap_vdd_info *vdd; + struct dentry *debug_dir; }; /** @@ -76,14 +110,52 @@ struct omap_volt_data { u8 vp_errgain; }; +/* + * Introduced in OMAP4, is a concept of a default channe - in OMAP4, this + * channel is MPU, all other domains such as IVA/CORE, could optionally + * link their i2c reg configuration to use MPU channel's configuration if + * required. To do this, mark in the PMIC structure's + * i2c_slave_addr/volt_reg_addr/cmd_reg_addr with this macro. + */ +#define USE_DEFAULT_CHANNEL_I2C_PARAM 0x10000 + +/* Min and max voltages from OMAP perspective */ +#define OMAP3430_VP1_VLIMITTO_VDDMIN 850000 +#define OMAP3430_VP1_VLIMITTO_VDDMAX 1425000 +#define OMAP3430_VP2_VLIMITTO_VDDMIN 900000 +#define OMAP3430_VP2_VLIMITTO_VDDMAX 1150000 + +#define OMAP3630_VP1_VLIMITTO_VDDMIN 900000 +#define OMAP3630_VP1_VLIMITTO_VDDMAX 1350000 +#define OMAP3630_VP2_VLIMITTO_VDDMIN 900000 +#define OMAP3630_VP2_VLIMITTO_VDDMAX 1200000 + +#define OMAP4_VP_MPU_VLIMITTO_VDDMIN 830000 +#define OMAP4_VP_MPU_VLIMITTO_VDDMAX 1410000 +#define OMAP4_VP_IVA_VLIMITTO_VDDMIN 830000 +#define OMAP4_VP_IVA_VLIMITTO_VDDMAX 1260000 +#define OMAP4_VP_CORE_VLIMITTO_VDDMIN 830000 +#define OMAP4_VP_CORE_VLIMITTO_VDDMAX 1200000 + +#define OMAP4_VP_CONFIG_ERROROFFSET 0x00 +#define OMAP4_VP_VSTEPMIN_VSTEPMIN 0x01 +#define OMAP4_VP_VSTEPMAX_VSTEPMAX 0x04 +#define OMAP4_VP_VLIMITTO_TIMEOUT_US 200 + /** - * struct omap_volt_pmic_info - PMIC specific data required by voltage driver. + * struct omap_voltdm_pmic - PMIC specific data required by voltage driver. * @slew_rate: PMIC slew rate (in uv/us) * @step_size: PMIC voltage step size (in uv) + * @i2c_high_speed: whether VC uses I2C high-speed mode to PMIC + * @i2c_mcode: master code value for I2C high-speed preamble transmission * @vsel_to_uv: PMIC API to convert vsel value to actual voltage in uV. * @uv_to_vsel: PMIC API to convert voltage in uV to vsel value. + * @i2c_hscll_low: PMIC interface speed config for highspeed mode (T low) + * @i2c_hscll_high: PMIC interface speed config for highspeed mode (T high) + * @i2c_scll_low: PMIC interface speed config for fullspeed mode (T low) + * @i2c_scll_high: PMIC interface speed config for fullspeed mode (T high) */ -struct omap_volt_pmic_info { +struct omap_voltdm_pmic { int slew_rate; int step_size; u32 on_volt; @@ -94,78 +166,78 @@ struct omap_volt_pmic_info { u8 vp_erroroffset; u8 vp_vstepmin; u8 vp_vstepmax; - u8 vp_vddmin; - u8 vp_vddmax; + u32 vp_vddmin; + u32 vp_vddmax; u8 vp_timeout_us; u8 i2c_slave_addr; - u8 pmic_reg; + u8 volt_reg_addr; + u8 cmd_reg_addr; + bool i2c_high_speed; + u8 i2c_hscll_low; + u8 i2c_hscll_high; + u8 i2c_scll_low; + u8 i2c_scll_high; + u8 i2c_mcode; unsigned long (*vsel_to_uv) (const u8 vsel); u8 (*uv_to_vsel) (unsigned long uV); }; /** + * struct omap_vdd_dep_volt - Map table for voltage dependencies + * @main_vdd_volt : The main vdd voltage + * @dep_vdd_volt : The voltage at which the dependent vdd should be + * when the main vdd is at <main_vdd_volt> voltage + * + * Table containing the parent vdd voltage and the dependent vdd voltage + * corresponding to it. + */ +struct omap_vdd_dep_volt { + u32 main_vdd_volt; + u32 dep_vdd_volt; +}; + +/** + * struct omap_vdd_dep_info - Dependent vdd info + * @name : Dependent vdd name + * @_dep_voltdm : internal structure meant to prevent multiple lookups + * @dep_table : Table containing the dependent vdd voltage + * corresponding to every main vdd voltage. + * @nr_dep_entries : number of dependency voltage entries + */ +struct omap_vdd_dep_info { + char *name; + struct voltagedomain *_dep_voltdm; + struct omap_vdd_dep_volt *dep_table; + int nr_dep_entries; +}; + +/** * omap_vdd_info - Per Voltage Domain info * * @volt_data : voltage table having the distinct voltages supported * by the domain and other associated per voltage data. - * @pmic_info : pmic specific parameters which should be populted by - * the pmic drivers. - * @vp_data : the register values, shifts, masks for various - * vp registers - * @vp_rt_data : VP data derived at runtime, not predefined - * @vc_data : structure containing various various vc registers, - * shifts, masks etc. - * @vfsm : voltage manager FSM data - * @voltdm : pointer to the voltage domain structure - * @debug_dir : debug directory for this voltage domain. - * @curr_volt : current voltage for this vdd. - * @vp_enabled : flag to keep track of whether vp is enabled or not - * @volt_scale : API to scale the voltage of the vdd. + * @dep_vdd_info : Array ending with a 0 terminator for dependency + * voltage information. */ struct omap_vdd_info { struct omap_volt_data *volt_data; - struct omap_volt_pmic_info *pmic_info; - struct omap_vp_instance_data *vp_data; - struct omap_vp_runtime_data vp_rt_data; - struct omap_vc_instance_data *vc_data; - const struct omap_vfsm_instance_data *vfsm; - struct voltagedomain voltdm; - struct dentry *debug_dir; - u32 curr_volt; - bool vp_enabled; - u32 (*read_reg) (u16 mod, u8 offset); - void (*write_reg) (u32 val, u16 mod, u8 offset); - int (*volt_scale) (struct omap_vdd_info *vdd, - unsigned long target_volt); + struct omap_vdd_dep_info *dep_vdd_info; }; -unsigned long omap_vp_get_curr_volt(struct voltagedomain *voltdm); -void omap_vp_enable(struct voltagedomain *voltdm); -void omap_vp_disable(struct voltagedomain *voltdm); -int omap_voltage_scale_vdd(struct voltagedomain *voltdm, - unsigned long target_volt); -void omap_voltage_reset(struct voltagedomain *voltdm); void omap_voltage_get_volttable(struct voltagedomain *voltdm, struct omap_volt_data **volt_data); struct omap_volt_data *omap_voltage_get_voltdata(struct voltagedomain *voltdm, unsigned long volt); unsigned long omap_voltage_get_nom_volt(struct voltagedomain *voltdm); -struct dentry *omap_voltage_get_dbgdir(struct voltagedomain *voltdm); -int __init omap_voltage_early_init(s16 prm_mod, s16 prm_irqst_mod, - struct omap_vdd_info *omap_vdd_array[], - u8 omap_vdd_count); #ifdef CONFIG_PM int omap_voltage_register_pmic(struct voltagedomain *voltdm, - struct omap_volt_pmic_info *pmic_info); + struct omap_voltdm_pmic *pmic); void omap_change_voltscale_method(struct voltagedomain *voltdm, int voltscale_method); -/* API to get the voltagedomain pointer */ -struct voltagedomain *omap_voltage_domain_lookup(char *name); - int omap_voltage_late_init(void); #else static inline int omap_voltage_register_pmic(struct voltagedomain *voltdm, - struct omap_volt_pmic_info *pmic_info) + struct omap_voltdm_pmic *pmic) { return -EINVAL; } @@ -175,10 +247,20 @@ static inline int omap_voltage_late_init(void) { return -EINVAL; } -static inline struct voltagedomain *omap_voltage_domain_lookup(char *name) -{ - return ERR_PTR(-EINVAL); -} #endif +extern void omap2xxx_voltagedomains_init(void); +extern void omap3xxx_voltagedomains_init(void); +extern void omap44xx_voltagedomains_init(void); + +struct voltagedomain *voltdm_lookup(const char *name); +void voltdm_init(struct voltagedomain **voltdm_list); +int voltdm_add_pwrdm(struct voltagedomain *voltdm, struct powerdomain *pwrdm); +int voltdm_for_each(int (*fn)(struct voltagedomain *voltdm, void *user), + void *user); +int voltdm_for_each_pwrdm(struct voltagedomain *voltdm, + int (*fn)(struct voltagedomain *voltdm, + struct powerdomain *pwrdm)); +int voltdm_scale(struct voltagedomain *voltdm, unsigned long target_volt); +void voltdm_reset(struct voltagedomain *voltdm); #endif diff --git a/arch/arm/mach-omap2/voltagedomains2xxx_data.c b/arch/arm/mach-omap2/voltagedomains2xxx_data.c new file mode 100644 index 000000000000..69ff261ff1cb --- /dev/null +++ b/arch/arm/mach-omap2/voltagedomains2xxx_data.c @@ -0,0 +1,32 @@ +/* + * OMAP3 voltage domain data + * + * Copyright (C) 2007, 2010 Texas Instruments, Inc. + + * 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. + */ +#include <linux/kernel.h> +#include <linux/init.h> + +#include "voltage.h" + +static struct voltagedomain omap2_voltdm_core = { + .name = "core", +}; + +static struct voltagedomain omap2_voltdm_wkup = { + .name = "wakeup", +}; + +static struct voltagedomain *voltagedomains_omap2[] __initdata = { + &omap2_voltdm_core, + &omap2_voltdm_wkup, + NULL, +}; + +void __init omap2xxx_voltagedomains_init(void) +{ + voltdm_init(voltagedomains_omap2); +} diff --git a/arch/arm/mach-omap2/voltagedomains3xxx_data.c b/arch/arm/mach-omap2/voltagedomains3xxx_data.c index def230fd2fde..8264d15ac6c2 100644 --- a/arch/arm/mach-omap2/voltagedomains3xxx_data.c +++ b/arch/arm/mach-omap2/voltagedomains3xxx_data.c @@ -31,50 +31,63 @@ * VDD data */ -static const struct omap_vfsm_instance_data omap3_vdd1_vfsm_data = { +static const struct omap_vfsm_instance omap3_vdd1_vfsm = { .voltsetup_reg = OMAP3_PRM_VOLTSETUP1_OFFSET, .voltsetup_shift = OMAP3430_SETUP_TIME1_SHIFT, .voltsetup_mask = OMAP3430_SETUP_TIME1_MASK, }; -static struct omap_vdd_info omap3_vdd1_info = { - .vp_data = &omap3_vp1_data, - .vc_data = &omap3_vc1_data, - .vfsm = &omap3_vdd1_vfsm_data, - .voltdm = { - .name = "mpu", - }, -}; +static struct omap_vdd_info omap3_vdd1_info; -static const struct omap_vfsm_instance_data omap3_vdd2_vfsm_data = { +static const struct omap_vfsm_instance omap3_vdd2_vfsm = { .voltsetup_reg = OMAP3_PRM_VOLTSETUP1_OFFSET, .voltsetup_shift = OMAP3430_SETUP_TIME2_SHIFT, .voltsetup_mask = OMAP3430_SETUP_TIME2_MASK, }; -static struct omap_vdd_info omap3_vdd2_info = { - .vp_data = &omap3_vp2_data, - .vc_data = &omap3_vc2_data, - .vfsm = &omap3_vdd2_vfsm_data, - .voltdm = { - .name = "core", - }, +static struct omap_vdd_info omap3_vdd2_info; + +static struct voltagedomain omap3_voltdm_mpu = { + .name = "mpu_iva", + .scalable = true, + .read = omap3_prm_vcvp_read, + .write = omap3_prm_vcvp_write, + .rmw = omap3_prm_vcvp_rmw, + .vc = &omap3_vc_mpu, + .vfsm = &omap3_vdd1_vfsm, + .vp = &omap3_vp_mpu, + .vdd = &omap3_vdd1_info, }; -/* OMAP3 VDD structures */ -static struct omap_vdd_info *omap3_vdd_info[] = { - &omap3_vdd1_info, - &omap3_vdd2_info, +static struct voltagedomain omap3_voltdm_core = { + .name = "core", + .scalable = true, + .read = omap3_prm_vcvp_read, + .write = omap3_prm_vcvp_write, + .rmw = omap3_prm_vcvp_rmw, + .vc = &omap3_vc_core, + .vfsm = &omap3_vdd2_vfsm, + .vp = &omap3_vp_core, + .vdd = &omap3_vdd2_info, }; -/* OMAP3 specific voltage init functions */ -static int __init omap3xxx_voltage_early_init(void) -{ - s16 prm_mod = OMAP3430_GR_MOD; - s16 prm_irqst_ocp_mod = OCP_MOD; +static struct voltagedomain omap3_voltdm_wkup = { + .name = "wakeup", +}; + +static struct voltagedomain *voltagedomains_omap3[] __initdata = { + &omap3_voltdm_mpu, + &omap3_voltdm_core, + &omap3_voltdm_wkup, + NULL, +}; + +static const char *sys_clk_name __initdata = "sys_ck"; - if (!cpu_is_omap34xx()) - return 0; +void __init omap3xxx_voltagedomains_init(void) +{ + struct voltagedomain *voltdm; + int i; /* * XXX Will depend on the process, validation, and binning @@ -83,13 +96,16 @@ static int __init omap3xxx_voltage_early_init(void) if (cpu_is_omap3630()) { omap3_vdd1_info.volt_data = omap36xx_vddmpu_volt_data; omap3_vdd2_info.volt_data = omap36xx_vddcore_volt_data; + omap3_vdd1_info.dep_vdd_info = omap36xx_vddmpu_dep_info; + } else { omap3_vdd1_info.volt_data = omap34xx_vddmpu_volt_data; omap3_vdd2_info.volt_data = omap34xx_vddcore_volt_data; + omap3_vdd1_info.dep_vdd_info = omap34xx_vddmpu_dep_info; } - return omap_voltage_early_init(prm_mod, prm_irqst_ocp_mod, - omap3_vdd_info, - ARRAY_SIZE(omap3_vdd_info)); + for (i = 0; voltdm = voltagedomains_omap3[i], voltdm; i++) + voltdm->sys_clk.name = sys_clk_name; + + voltdm_init(voltagedomains_omap3); }; -core_initcall(omap3xxx_voltage_early_init); diff --git a/arch/arm/mach-omap2/voltagedomains44xx_data.c b/arch/arm/mach-omap2/voltagedomains44xx_data.c index cb64996de0e1..b3500e0abb0c 100644 --- a/arch/arm/mach-omap2/voltagedomains44xx_data.c +++ b/arch/arm/mach-omap2/voltagedomains44xx_data.c @@ -20,6 +20,7 @@ #include <linux/kernel.h> #include <linux/err.h> #include <linux/init.h> +#include <linux/clk.h> #include <plat/common.h> @@ -32,71 +33,105 @@ #include "vc.h" #include "vp.h" -static const struct omap_vfsm_instance_data omap4_vdd_mpu_vfsm_data = { +static const struct omap_vfsm_instance omap4_vdd_mpu_vfsm = { .voltsetup_reg = OMAP4_PRM_VOLTSETUP_MPU_RET_SLEEP_OFFSET, }; -static struct omap_vdd_info omap4_vdd_mpu_info = { - .vp_data = &omap4_vp_mpu_data, - .vc_data = &omap4_vc_mpu_data, - .vfsm = &omap4_vdd_mpu_vfsm_data, - .voltdm = { - .name = "mpu", - }, -}; +static struct omap_vdd_info omap4_vdd_mpu_info; -static const struct omap_vfsm_instance_data omap4_vdd_iva_vfsm_data = { +static const struct omap_vfsm_instance omap4_vdd_iva_vfsm = { .voltsetup_reg = OMAP4_PRM_VOLTSETUP_IVA_RET_SLEEP_OFFSET, }; -static struct omap_vdd_info omap4_vdd_iva_info = { - .vp_data = &omap4_vp_iva_data, - .vc_data = &omap4_vc_iva_data, - .vfsm = &omap4_vdd_iva_vfsm_data, - .voltdm = { - .name = "iva", - }, -}; +static struct omap_vdd_info omap4_vdd_iva_info; -static const struct omap_vfsm_instance_data omap4_vdd_core_vfsm_data = { +static const struct omap_vfsm_instance omap4_vdd_core_vfsm = { .voltsetup_reg = OMAP4_PRM_VOLTSETUP_CORE_RET_SLEEP_OFFSET, }; -static struct omap_vdd_info omap4_vdd_core_info = { - .vp_data = &omap4_vp_core_data, - .vc_data = &omap4_vc_core_data, - .vfsm = &omap4_vdd_core_vfsm_data, - .voltdm = { - .name = "core", - }, +static struct omap_vdd_info omap4_vdd_core_info; + +static struct voltagedomain omap4_voltdm_mpu = { + .name = "mpu", + .scalable = true, + .read = omap4_prm_vcvp_read, + .write = omap4_prm_vcvp_write, + .rmw = omap4_prm_vcvp_rmw, + .vc = &omap4_vc_mpu, + .vfsm = &omap4_vdd_mpu_vfsm, + .vp = &omap4_vp_mpu, + .vdd = &omap4_vdd_mpu_info, +}; + +static struct voltagedomain omap4_voltdm_iva = { + .name = "iva", + .scalable = true, + .read = omap4_prm_vcvp_read, + .write = omap4_prm_vcvp_write, + .rmw = omap4_prm_vcvp_rmw, + .vc = &omap4_vc_iva, + .vfsm = &omap4_vdd_iva_vfsm, + .vp = &omap4_vp_iva, + .vdd = &omap4_vdd_iva_info, }; -/* OMAP4 VDD structures */ -static struct omap_vdd_info *omap4_vdd_info[] = { - &omap4_vdd_mpu_info, - &omap4_vdd_iva_info, - &omap4_vdd_core_info, +static struct voltagedomain omap4_voltdm_core = { + .name = "core", + .scalable = true, + .read = omap4_prm_vcvp_read, + .write = omap4_prm_vcvp_write, + .rmw = omap4_prm_vcvp_rmw, + .vc = &omap4_vc_core, + .vfsm = &omap4_vdd_core_vfsm, + .vp = &omap4_vp_core, + .vdd = &omap4_vdd_core_info, }; -/* OMAP4 specific voltage init functions */ -static int __init omap44xx_voltage_early_init(void) -{ - s16 prm_mod = OMAP4430_PRM_DEVICE_INST; - s16 prm_irqst_ocp_mod = OMAP4430_PRM_OCP_SOCKET_INST; +static struct voltagedomain omap4_voltdm_wkup = { + .name = "wakeup", +}; + +static struct voltagedomain *voltagedomains_omap4[] __initdata = { + &omap4_voltdm_mpu, + &omap4_voltdm_iva, + &omap4_voltdm_core, + &omap4_voltdm_wkup, + NULL, +}; - if (!cpu_is_omap44xx()) - return 0; +static const char *sys_clk_name __initdata = "sys_clkin_ck"; + +void __init omap44xx_voltagedomains_init(void) +{ + struct voltagedomain *voltdm; + int i; /* * XXX Will depend on the process, validation, and binning * for the currently-running IC */ - omap4_vdd_mpu_info.volt_data = omap44xx_vdd_mpu_volt_data; - omap4_vdd_iva_info.volt_data = omap44xx_vdd_iva_volt_data; - omap4_vdd_core_info.volt_data = omap44xx_vdd_core_volt_data; + if (cpu_is_omap443x()) { + omap4_vdd_mpu_info.volt_data = omap443x_vdd_mpu_volt_data; + omap4_vdd_iva_info.volt_data = omap443x_vdd_iva_volt_data; + omap4_vdd_core_info.volt_data = omap443x_vdd_core_volt_data; +#if 0 + /* + * TODO: XXX: we *should* fix O4 core domain clks before doing + * this - already tested dvfs logic.. + */ + omap4_vdd_mpu_info.dep_vdd_info = omap443x_vddmpu_dep_info; + omap4_vdd_iva_info.dep_vdd_info = omap443x_vddiva_dep_info; +#endif + } else if (cpu_is_omap446x()) { + omap4_vdd_mpu_info.volt_data = omap446x_vdd_mpu_volt_data; + omap4_vdd_iva_info.volt_data = omap446x_vdd_iva_volt_data; + omap4_vdd_core_info.volt_data = omap446x_vdd_core_volt_data; + } else { + return; + } + + for (i = 0; voltdm = voltagedomains_omap4[i], voltdm; i++) + voltdm->sys_clk.name = sys_clk_name; - return omap_voltage_early_init(prm_mod, prm_irqst_ocp_mod, - omap4_vdd_info, - ARRAY_SIZE(omap4_vdd_info)); + voltdm_init(voltagedomains_omap4); }; -core_initcall(omap44xx_voltage_early_init); diff --git a/arch/arm/mach-omap2/vp.c b/arch/arm/mach-omap2/vp.c new file mode 100644 index 000000000000..467792136ed8 --- /dev/null +++ b/arch/arm/mach-omap2/vp.c @@ -0,0 +1,329 @@ +#include <linux/kernel.h> +#include <linux/init.h> + +#include <plat/common.h> + +#include "voltage.h" +#include "vp.h" +#include "prm-regbits-34xx.h" +#include "prm-regbits-44xx.h" +#include "prm44xx.h" + +static void vp_latch_vsel(struct voltagedomain *voltdm) +{ + struct omap_vp_instance *vp = voltdm->vp; + u32 vpconfig; + unsigned long uvdc; + char vsel; + + uvdc = omap_voltage_get_nom_volt(voltdm); + if (!uvdc) { + pr_warning("%s: unable to find current voltage for vdd_%s\n", + __func__, voltdm->name); + return; + } + + if (!voltdm->pmic || !voltdm->pmic->uv_to_vsel) { + pr_warning("%s: PMIC function to convert voltage in uV to" + " vsel not registered\n", __func__); + return; + } + + vsel = voltdm->pmic->uv_to_vsel(uvdc); + + vpconfig = voltdm->read(vp->vpconfig); + vpconfig &= ~(vp->common->vpconfig_initvoltage_mask | + vp->common->vpconfig_initvdd); + vpconfig |= vsel << __ffs(vp->common->vpconfig_initvoltage_mask); + voltdm->write(vpconfig, vp->vpconfig); + + /* Trigger initVDD value copy to voltage processor */ + voltdm->write((vpconfig | vp->common->vpconfig_initvdd), + vp->vpconfig); + + /* Clear initVDD copy trigger bit */ + voltdm->write(vpconfig, vp->vpconfig); +} + +/* Generic voltage init functions */ +void __init omap_vp_init(struct voltagedomain *voltdm) +{ + struct omap_vp_instance *vp = voltdm->vp; + u32 val, sys_clk_rate, timeout, waittime; + u32 vddmin, vddmax, vstepmin, vstepmax; + + if (!voltdm->read || !voltdm->write) { + pr_err("%s: No read/write API for accessing vdd_%s regs\n", + __func__, voltdm->name); + return; + } + + vp->enabled = false; + + /* Divide to avoid overflow */ + sys_clk_rate = voltdm->sys_clk.rate / 1000; + + timeout = (sys_clk_rate * voltdm->pmic->vp_timeout_us) / 1000; + vddmin = voltdm->pmic->uv_to_vsel(voltdm->pmic->vp_vddmin); + vddmax = voltdm->pmic->uv_to_vsel(voltdm->pmic->vp_vddmax); + + waittime = ((voltdm->pmic->step_size / voltdm->pmic->slew_rate) * + sys_clk_rate) / 1000; + vstepmin = voltdm->pmic->vp_vstepmin; + vstepmax = voltdm->pmic->vp_vstepmax; + + /* + * VP_CONFIG: error gain is not set here, it will be updated + * on each scale, based on OPP. + */ + val = (voltdm->pmic->vp_erroroffset << + __ffs(voltdm->vp->common->vpconfig_erroroffset_mask)) | + vp->common->vpconfig_timeouten; + voltdm->write(val, vp->vpconfig); + + /* VSTEPMIN */ + val = (waittime << vp->common->vstepmin_smpswaittimemin_shift) | + (vstepmin << vp->common->vstepmin_stepmin_shift); + voltdm->write(val, vp->vstepmin); + + /* VSTEPMAX */ + val = (vstepmax << vp->common->vstepmax_stepmax_shift) | + (waittime << vp->common->vstepmax_smpswaittimemax_shift); + voltdm->write(val, vp->vstepmax); + + /* VLIMITTO */ + val = (vddmax << vp->common->vlimitto_vddmax_shift) | + (vddmin << vp->common->vlimitto_vddmin_shift) | + (timeout << vp->common->vlimitto_timeout_shift); + voltdm->write(val, vp->vlimitto); +} + +int omap_vp_update_errorgain(struct voltagedomain *voltdm, + unsigned long target_volt) +{ + struct omap_volt_data *volt_data; + + /* Get volt_data corresponding to target_volt */ + volt_data = omap_voltage_get_voltdata(voltdm, target_volt); + if (IS_ERR(volt_data)) + return -EINVAL; + + /* Setting vp errorgain based on the voltage */ + voltdm->rmw(voltdm->vp->common->vpconfig_errorgain_mask, + volt_data->vp_errgain << + __ffs(voltdm->vp->common->vpconfig_errorgain_mask), + voltdm->vp->vpconfig); + + return 0; +} + +/* VP force update method of voltage scaling */ +int omap_vp_forceupdate_scale(struct voltagedomain *voltdm, + unsigned long target_volt) +{ + struct omap_vp_instance *vp = voltdm->vp; + u32 vpconfig; + u8 target_vsel, current_vsel; + int ret, timeout = 0; + + ret = omap_vc_pre_scale(voltdm, target_volt, &target_vsel, ¤t_vsel); + if (ret) + return ret; + + /* + * Clear all pending TransactionDone interrupt/status. Typical latency + * is <3us + */ + while (timeout++ < VP_TRANXDONE_TIMEOUT) { + vp->common->ops->clear_txdone(vp->id); + if (!vp->common->ops->check_txdone(vp->id)) + break; + udelay(1); + } + if (timeout >= VP_TRANXDONE_TIMEOUT) { + pr_warning("%s: vdd_%s TRANXDONE timeout exceeded." + "Voltage change aborted", __func__, voltdm->name); + return -ETIMEDOUT; + } + + /* Configure for VP-Force Update */ + vpconfig = voltdm->read(vp->vpconfig); + vpconfig &= ~(vp->common->vpconfig_initvdd | + vp->common->vpconfig_forceupdate | + vp->common->vpconfig_initvoltage_mask); + vpconfig |= ((target_vsel << + __ffs(vp->common->vpconfig_initvoltage_mask))); + voltdm->write(vpconfig, vp->vpconfig); + + /* Trigger initVDD value copy to voltage processor */ + vpconfig |= vp->common->vpconfig_initvdd; + voltdm->write(vpconfig, vp->vpconfig); + + /* Force update of voltage */ + vpconfig |= vp->common->vpconfig_forceupdate; + voltdm->write(vpconfig, vp->vpconfig); + + /* + * Wait for TransactionDone. Typical latency is <200us. + * Depends on SMPSWAITTIMEMIN/MAX and voltage change + */ + timeout = 0; + omap_test_timeout(vp->common->ops->check_txdone(vp->id), + VP_TRANXDONE_TIMEOUT, timeout); + if (timeout >= VP_TRANXDONE_TIMEOUT) + pr_err("%s: vdd_%s TRANXDONE timeout exceeded." + "TRANXDONE never got set after the voltage update\n", + __func__, voltdm->name); + + omap_vc_post_scale(voltdm, target_volt, target_vsel, current_vsel); + + /* + * Disable TransactionDone interrupt , clear all status, clear + * control registers + */ + timeout = 0; + while (timeout++ < VP_TRANXDONE_TIMEOUT) { + vp->common->ops->clear_txdone(vp->id); + if (!vp->common->ops->check_txdone(vp->id)) + break; + udelay(1); + } + + if (timeout >= VP_TRANXDONE_TIMEOUT) + pr_warning("%s: vdd_%s TRANXDONE timeout exceeded while trying" + "to clear the TRANXDONE status\n", + __func__, voltdm->name); + + vpconfig = voltdm->read(vp->vpconfig); + /* Clear initVDD copy trigger bit */ + vpconfig &= ~vp->common->vpconfig_initvdd; + voltdm->write(vpconfig, vp->vpconfig); + /* Clear force bit */ + vpconfig &= ~vp->common->vpconfig_forceupdate; + voltdm->write(vpconfig, vp->vpconfig); + + return 0; +} + +/** + * omap_vp_get_curr_volt() - API to get the current vp voltage. + * @voltdm: pointer to the VDD. + * + * This API returns the current voltage for the specified voltage processor + */ +unsigned long omap_vp_get_curr_volt(struct voltagedomain *voltdm) +{ + struct omap_vp_instance *vp = voltdm->vp; + u8 curr_vsel; + + if (!voltdm || IS_ERR(voltdm)) { + pr_warning("%s: VDD specified does not exist!\n", __func__); + return 0; + } + + if (!voltdm->read) { + pr_err("%s: No read API for reading vdd_%s regs\n", + __func__, voltdm->name); + return 0; + } + + curr_vsel = (voltdm->read(vp->voltage) & vp->common->vpvoltage_mask) + >> __ffs(vp->common->vpvoltage_mask); + + if (!voltdm->pmic || !voltdm->pmic->vsel_to_uv) { + pr_warning("%s: PMIC function to convert vsel to voltage" + "in uV not registerd\n", __func__); + return 0; + } + + return voltdm->pmic->vsel_to_uv(curr_vsel); +} + +/** + * omap_vp_enable() - API to enable a particular VP + * @voltdm: pointer to the VDD whose VP is to be enabled. + * + * This API enables a particular voltage processor. Needed by the smartreflex + * class drivers. + */ +void omap_vp_enable(struct voltagedomain *voltdm) +{ + struct omap_vp_instance *vp; + u32 vpconfig; + + if (!voltdm || IS_ERR(voltdm)) { + pr_warning("%s: VDD specified does not exist!\n", __func__); + return; + } + + vp = voltdm->vp; + if (!voltdm->read || !voltdm->write) { + pr_err("%s: No read/write API for accessing vdd_%s regs\n", + __func__, voltdm->name); + return; + } + + /* If VP is already enabled, do nothing. Return */ + if (vp->enabled) + return; + + vp_latch_vsel(voltdm); + + /* Enable VP */ + vpconfig = voltdm->read(vp->vpconfig); + vpconfig |= vp->common->vpconfig_vpenable; + voltdm->write(vpconfig, vp->vpconfig); + vp->enabled = true; +} + +/** + * omap_vp_disable() - API to disable a particular VP + * @voltdm: pointer to the VDD whose VP is to be disabled. + * + * This API disables a particular voltage processor. Needed by the smartreflex + * class drivers. + */ +void omap_vp_disable(struct voltagedomain *voltdm) +{ + struct omap_vp_instance *vp; + u32 vpconfig; + int timeout; + + if (!voltdm || IS_ERR(voltdm)) { + pr_warning("%s: VDD specified does not exist!\n", __func__); + return; + } + + vp = voltdm->vp; + if (!voltdm->read || !voltdm->write) { + pr_err("%s: No read/write API for accessing vdd_%s regs\n", + __func__, voltdm->name); + return; + } + + /* If VP is already disabled, do nothing. Return */ + if (!vp->enabled) { + pr_warning("%s: Trying to disable VP for vdd_%s when" + "it is already disabled\n", __func__, voltdm->name); + return; + } + + /* Disable VP */ + vpconfig = voltdm->read(vp->vpconfig); + vpconfig &= ~vp->common->vpconfig_vpenable; + voltdm->write(vpconfig, vp->vpconfig); + + /* + * Wait for VP idle Typical latency is <2us. Maximum latency is ~100us + */ + omap_test_timeout((voltdm->read(vp->vstatus)), + VP_IDLE_TIMEOUT, timeout); + + if (timeout >= VP_IDLE_TIMEOUT) + pr_warning("%s: vdd_%s idle timedout\n", + __func__, voltdm->name); + + vp->enabled = false; + + return; +} diff --git a/arch/arm/mach-omap2/vp.h b/arch/arm/mach-omap2/vp.h index 7ce134f7de79..f78752bc9e79 100644 --- a/arch/arm/mach-omap2/vp.h +++ b/arch/arm/mach-omap2/vp.h @@ -19,44 +19,60 @@ #include <linux/kernel.h> +struct voltagedomain; + +/* + * Voltage Processor (VP) identifiers + */ +#define OMAP3_VP_VDD_MPU_ID 0 +#define OMAP3_VP_VDD_CORE_ID 1 +#define OMAP4_VP_VDD_CORE_ID 0 +#define OMAP4_VP_VDD_IVA_ID 1 +#define OMAP4_VP_VDD_MPU_ID 2 + /* XXX document */ #define VP_IDLE_TIMEOUT 200 #define VP_TRANXDONE_TIMEOUT 300 +/** + * struct omap_vp_ops - per-VP operations + * @check_txdone: check for VP transaction done + * @clear_txdone: clear VP transaction done status + */ +struct omap_vp_ops { + u32 (*check_txdone)(u8 vp_id); + void (*clear_txdone)(u8 vp_id); +}; /** - * struct omap_vp_common_data - register data common to all VDDs + * struct omap_vp_common - register data common to all VDDs + * @vpconfig_erroroffset_mask: ERROROFFSET bitmask in the PRM_VP*_CONFIG reg * @vpconfig_errorgain_mask: ERRORGAIN bitmask in the PRM_VP*_CONFIG reg * @vpconfig_initvoltage_mask: INITVOLTAGE bitmask in the PRM_VP*_CONFIG reg - * @vpconfig_timeouten_mask: TIMEOUT bitmask in the PRM_VP*_CONFIG reg + * @vpconfig_timeouten: TIMEOUT bitmask in the PRM_VP*_CONFIG reg * @vpconfig_initvdd: INITVDD bitmask in the PRM_VP*_CONFIG reg * @vpconfig_forceupdate: FORCEUPDATE bitmask in the PRM_VP*_CONFIG reg * @vpconfig_vpenable: VPENABLE bitmask in the PRM_VP*_CONFIG reg * @vpconfig_erroroffset_shift: ERROROFFSET field shift in PRM_VP*_CONFIG reg * @vpconfig_errorgain_shift: ERRORGAIN field shift in PRM_VP*_CONFIG reg * @vpconfig_initvoltage_shift: INITVOLTAGE field shift in PRM_VP*_CONFIG reg - * @vpconfig_stepmin_shift: VSTEPMIN field shift in the PRM_VP*_VSTEPMIN reg - * @vpconfig_smpswaittimemin_shift: SMPSWAITTIMEMIN field shift in PRM_VP*_VSTEPMIN reg - * @vpconfig_stepmax_shift: VSTEPMAX field shift in the PRM_VP*_VSTEPMAX reg - * @vpconfig_smpswaittimemax_shift: SMPSWAITTIMEMAX field shift in PRM_VP*_VSTEPMAX reg - * @vpconfig_vlimitto_vddmin_shift: VDDMIN field shift in PRM_VP*_VLIMITTO reg - * @vpconfig_vlimitto_vddmax_shift: VDDMAX field shift in PRM_VP*_VLIMITTO reg - * @vpconfig_vlimitto_timeout_shift: TIMEOUT field shift in PRM_VP*_VLIMITTO reg - * - * XXX It it not necessary to have both a mask and a shift for the same - * bitfield - remove one - * XXX Many of these fields are wrongly named -- e.g., vpconfig_smps* -- fix! + * @vstepmin_stepmin_shift: VSTEPMIN field shift in the PRM_VP*_VSTEPMIN reg + * @vstepmin_smpswaittimemin_shift: SMPSWAITTIMEMIN field shift in PRM_VP*_VSTEPMIN reg + * @vstepmax_stepmax_shift: VSTEPMAX field shift in the PRM_VP*_VSTEPMAX reg + * @vstepmax_smpswaittimemax_shift: SMPSWAITTIMEMAX field shift in PRM_VP*_VSTEPMAX reg + * @vlimitto_vddmin_shift: VDDMIN field shift in PRM_VP*_VLIMITTO reg + * @vlimitto_vddmax_shift: VDDMAX field shift in PRM_VP*_VLIMITTO reg + * @vlimitto_timeout_shift: TIMEOUT field shift in PRM_VP*_VLIMITTO reg + * @vpvoltage_mask: VPVOLTAGE field mask in PRM_VP*_VOLTAGE reg */ -struct omap_vp_common_data { +struct omap_vp_common { + u32 vpconfig_erroroffset_mask; u32 vpconfig_errorgain_mask; u32 vpconfig_initvoltage_mask; - u32 vpconfig_timeouten; - u32 vpconfig_initvdd; - u32 vpconfig_forceupdate; - u32 vpconfig_vpenable; - u8 vpconfig_erroroffset_shift; - u8 vpconfig_errorgain_shift; - u8 vpconfig_initvoltage_shift; + u8 vpconfig_timeouten; + u8 vpconfig_initvdd; + u8 vpconfig_forceupdate; + u8 vpconfig_vpenable; u8 vstepmin_stepmin_shift; u8 vstepmin_smpswaittimemin_shift; u8 vstepmax_stepmax_shift; @@ -64,80 +80,49 @@ struct omap_vp_common_data { u8 vlimitto_vddmin_shift; u8 vlimitto_vddmax_shift; u8 vlimitto_timeout_shift; -}; + u8 vpvoltage_mask; -/** - * struct omap_vp_prm_irqst_data - PRM_IRQSTATUS_MPU.VP_TRANXDONE_ST data - * @prm_irqst_reg: reg offset for PRM_IRQSTATUS_MPU from top of PRM - * @tranxdone_status: VP_TRANXDONE_ST bitmask in PRM_IRQSTATUS_MPU reg - * - * XXX prm_irqst_reg does not belong here - * XXX Note that on OMAP3, VP_TRANXDONE interrupt may not work due to a - * hardware bug - * XXX This structure is probably not needed - */ -struct omap_vp_prm_irqst_data { - u8 prm_irqst_reg; - u32 tranxdone_status; + const struct omap_vp_ops *ops; }; /** - * struct omap_vp_instance_data - VP register offsets (per-VDD) - * @vp_common: pointer to struct omap_vp_common_data * for this SoC - * @prm_irqst_data: pointer to struct omap_vp_prm_irqst_data for this VDD + * struct omap_vp_instance - VP register offsets (per-VDD) + * @common: pointer to struct omap_vp_common * for this SoC * @vpconfig: PRM_VP*_CONFIG reg offset from PRM start * @vstepmin: PRM_VP*_VSTEPMIN reg offset from PRM start * @vlimitto: PRM_VP*_VLIMITTO reg offset from PRM start * @vstatus: PRM_VP*_VSTATUS reg offset from PRM start * @voltage: PRM_VP*_VOLTAGE reg offset from PRM start + * @enabled: flag to keep track of whether vp is enabled or not * * XXX vp_common is probably not needed since it is per-SoC */ -struct omap_vp_instance_data { - const struct omap_vp_common_data *vp_common; - const struct omap_vp_prm_irqst_data *prm_irqst_data; +struct omap_vp_instance { + const struct omap_vp_common *common; u8 vpconfig; u8 vstepmin; u8 vstepmax; u8 vlimitto; u8 vstatus; u8 voltage; + u8 id; + bool enabled; }; -/** - * struct omap_vp_runtime_data - VP data populated at runtime by code - * @vpconfig_erroroffset: value of ERROROFFSET bitfield in PRM_VP*_CONFIG - * @vpconfig_errorgain: value of ERRORGAIN bitfield in PRM_VP*_CONFIG - * @vstepmin_smpswaittimemin: value of SMPSWAITTIMEMIN bitfield in PRM_VP*_VSTEPMIN - * @vstepmax_smpswaittimemax: value of SMPSWAITTIMEMAX bitfield in PRM_VP*_VSTEPMAX - * @vlimitto_timeout: value of TIMEOUT bitfield in PRM_VP*_VLIMITTO - * @vstepmin_stepmin: value of VSTEPMIN bitfield in PRM_VP*_VSTEPMIN - * @vstepmax_stepmax: value of VSTEPMAX bitfield in PRM_VP*_VSTEPMAX - * @vlimitto_vddmin: value of VDDMIN bitfield in PRM_VP*_VLIMITTO - * @vlimitto_vddmax: value of VDDMAX bitfield in PRM_VP*_VLIMITTO - * - * XXX Is this structure really needed? Why not just program the - * device directly? They are in PRM space, therefore in the WKUP - * powerdomain, so register contents should not be lost in off-mode. - * XXX Some of these fields are incorrectly named, e.g., vstep* - */ -struct omap_vp_runtime_data { - u32 vpconfig_erroroffset; - u16 vpconfig_errorgain; - u16 vstepmin_smpswaittimemin; - u16 vstepmax_smpswaittimemax; - u16 vlimitto_timeout; - u8 vstepmin_stepmin; - u8 vstepmax_stepmax; - u8 vlimitto_vddmin; - u8 vlimitto_vddmax; -}; +extern struct omap_vp_instance omap3_vp_mpu; +extern struct omap_vp_instance omap3_vp_core; -extern struct omap_vp_instance_data omap3_vp1_data; -extern struct omap_vp_instance_data omap3_vp2_data; +extern struct omap_vp_instance omap4_vp_mpu; +extern struct omap_vp_instance omap4_vp_iva; +extern struct omap_vp_instance omap4_vp_core; -extern struct omap_vp_instance_data omap4_vp_mpu_data; -extern struct omap_vp_instance_data omap4_vp_iva_data; -extern struct omap_vp_instance_data omap4_vp_core_data; +void omap_vp_init(struct voltagedomain *voltdm); +void omap_vp_enable(struct voltagedomain *voltdm); +void omap_vp_disable(struct voltagedomain *voltdm); +unsigned long omap_vp_get_curr_volt(struct voltagedomain *voltdm); +int omap_vp_forceupdate_scale(struct voltagedomain *voltdm, + unsigned long target_volt); +int omap_vp_update_errorgain(struct voltagedomain *voltdm, + unsigned long target_volt); #endif diff --git a/arch/arm/mach-omap2/vp3xxx_data.c b/arch/arm/mach-omap2/vp3xxx_data.c index 645217094e51..260c554b1547 100644 --- a/arch/arm/mach-omap2/vp3xxx_data.c +++ b/arch/arm/mach-omap2/vp3xxx_data.c @@ -25,16 +25,20 @@ #include "voltage.h" #include "vp.h" +#include "prm2xxx_3xxx.h" + +static const struct omap_vp_ops omap3_vp_ops = { + .check_txdone = omap3_prm_vp_check_txdone, + .clear_txdone = omap3_prm_vp_clear_txdone, +}; /* * VP data common to 34xx/36xx chips * XXX This stuff presumably belongs in the vp3xxx.c or vp.c file. */ -static const struct omap_vp_common_data omap3_vp_common = { - .vpconfig_erroroffset_shift = OMAP3430_ERROROFFSET_SHIFT, +static const struct omap_vp_common omap3_vp_common = { + .vpconfig_erroroffset_mask = OMAP3430_ERROROFFSET_MASK, .vpconfig_errorgain_mask = OMAP3430_ERRORGAIN_MASK, - .vpconfig_errorgain_shift = OMAP3430_ERRORGAIN_SHIFT, - .vpconfig_initvoltage_shift = OMAP3430_INITVOLTAGE_SHIFT, .vpconfig_initvoltage_mask = OMAP3430_INITVOLTAGE_MASK, .vpconfig_timeouten = OMAP3430_TIMEOUTEN_MASK, .vpconfig_initvdd = OMAP3430_INITVDD_MASK, @@ -47,36 +51,29 @@ static const struct omap_vp_common_data omap3_vp_common = { .vlimitto_vddmin_shift = OMAP3430_VDDMIN_SHIFT, .vlimitto_vddmax_shift = OMAP3430_VDDMAX_SHIFT, .vlimitto_timeout_shift = OMAP3430_TIMEOUT_SHIFT, -}; + .vpvoltage_mask = OMAP3430_VPVOLTAGE_MASK, -static const struct omap_vp_prm_irqst_data omap3_vp1_prm_irqst_data = { - .prm_irqst_reg = OMAP3_PRM_IRQSTATUS_MPU_OFFSET, - .tranxdone_status = OMAP3430_VP1_TRANXDONE_ST_MASK, + .ops = &omap3_vp_ops, }; -struct omap_vp_instance_data omap3_vp1_data = { - .vp_common = &omap3_vp_common, +struct omap_vp_instance omap3_vp_mpu = { + .id = OMAP3_VP_VDD_MPU_ID, + .common = &omap3_vp_common, .vpconfig = OMAP3_PRM_VP1_CONFIG_OFFSET, .vstepmin = OMAP3_PRM_VP1_VSTEPMIN_OFFSET, .vstepmax = OMAP3_PRM_VP1_VSTEPMAX_OFFSET, .vlimitto = OMAP3_PRM_VP1_VLIMITTO_OFFSET, .vstatus = OMAP3_PRM_VP1_STATUS_OFFSET, .voltage = OMAP3_PRM_VP1_VOLTAGE_OFFSET, - .prm_irqst_data = &omap3_vp1_prm_irqst_data, -}; - -static const struct omap_vp_prm_irqst_data omap3_vp2_prm_irqst_data = { - .prm_irqst_reg = OMAP3_PRM_IRQSTATUS_MPU_OFFSET, - .tranxdone_status = OMAP3430_VP2_TRANXDONE_ST_MASK, }; -struct omap_vp_instance_data omap3_vp2_data = { - .vp_common = &omap3_vp_common, +struct omap_vp_instance omap3_vp_core = { + .id = OMAP3_VP_VDD_CORE_ID, + .common = &omap3_vp_common, .vpconfig = OMAP3_PRM_VP2_CONFIG_OFFSET, .vstepmin = OMAP3_PRM_VP2_VSTEPMIN_OFFSET, .vstepmax = OMAP3_PRM_VP2_VSTEPMAX_OFFSET, .vlimitto = OMAP3_PRM_VP2_VLIMITTO_OFFSET, .vstatus = OMAP3_PRM_VP2_STATUS_OFFSET, .voltage = OMAP3_PRM_VP2_VOLTAGE_OFFSET, - .prm_irqst_data = &omap3_vp2_prm_irqst_data, }; diff --git a/arch/arm/mach-omap2/vp44xx_data.c b/arch/arm/mach-omap2/vp44xx_data.c index 65d1ad63800a..b4e77044891e 100644 --- a/arch/arm/mach-omap2/vp44xx_data.c +++ b/arch/arm/mach-omap2/vp44xx_data.c @@ -27,15 +27,18 @@ #include "vp.h" +static const struct omap_vp_ops omap4_vp_ops = { + .check_txdone = omap4_prm_vp_check_txdone, + .clear_txdone = omap4_prm_vp_clear_txdone, +}; + /* * VP data common to 44xx chips * XXX This stuff presumably belongs in the vp44xx.c or vp.c file. */ -static const struct omap_vp_common_data omap4_vp_common = { - .vpconfig_erroroffset_shift = OMAP4430_ERROROFFSET_SHIFT, +static const struct omap_vp_common omap4_vp_common = { + .vpconfig_erroroffset_mask = OMAP4430_ERROROFFSET_MASK, .vpconfig_errorgain_mask = OMAP4430_ERRORGAIN_MASK, - .vpconfig_errorgain_shift = OMAP4430_ERRORGAIN_SHIFT, - .vpconfig_initvoltage_shift = OMAP4430_INITVOLTAGE_SHIFT, .vpconfig_initvoltage_mask = OMAP4430_INITVOLTAGE_MASK, .vpconfig_timeouten = OMAP4430_TIMEOUTEN_MASK, .vpconfig_initvdd = OMAP4430_INITVDD_MASK, @@ -48,53 +51,39 @@ static const struct omap_vp_common_data omap4_vp_common = { .vlimitto_vddmin_shift = OMAP4430_VDDMIN_SHIFT, .vlimitto_vddmax_shift = OMAP4430_VDDMAX_SHIFT, .vlimitto_timeout_shift = OMAP4430_TIMEOUT_SHIFT, + .vpvoltage_mask = OMAP4430_VPVOLTAGE_MASK, + .ops = &omap4_vp_ops, }; -static const struct omap_vp_prm_irqst_data omap4_vp_mpu_prm_irqst_data = { - .prm_irqst_reg = OMAP4_PRM_IRQSTATUS_MPU_2_OFFSET, - .tranxdone_status = OMAP4430_VP_MPU_TRANXDONE_ST_MASK, -}; - -struct omap_vp_instance_data omap4_vp_mpu_data = { - .vp_common = &omap4_vp_common, +struct omap_vp_instance omap4_vp_mpu = { + .id = OMAP4_VP_VDD_MPU_ID, + .common = &omap4_vp_common, .vpconfig = OMAP4_PRM_VP_MPU_CONFIG_OFFSET, .vstepmin = OMAP4_PRM_VP_MPU_VSTEPMIN_OFFSET, .vstepmax = OMAP4_PRM_VP_MPU_VSTEPMAX_OFFSET, .vlimitto = OMAP4_PRM_VP_MPU_VLIMITTO_OFFSET, .vstatus = OMAP4_PRM_VP_MPU_STATUS_OFFSET, .voltage = OMAP4_PRM_VP_MPU_VOLTAGE_OFFSET, - .prm_irqst_data = &omap4_vp_mpu_prm_irqst_data, }; -static const struct omap_vp_prm_irqst_data omap4_vp_iva_prm_irqst_data = { - .prm_irqst_reg = OMAP4_PRM_IRQSTATUS_MPU_OFFSET, - .tranxdone_status = OMAP4430_VP_IVA_TRANXDONE_ST_MASK, -}; - -struct omap_vp_instance_data omap4_vp_iva_data = { - .vp_common = &omap4_vp_common, +struct omap_vp_instance omap4_vp_iva = { + .id = OMAP4_VP_VDD_IVA_ID, + .common = &omap4_vp_common, .vpconfig = OMAP4_PRM_VP_IVA_CONFIG_OFFSET, .vstepmin = OMAP4_PRM_VP_IVA_VSTEPMIN_OFFSET, .vstepmax = OMAP4_PRM_VP_IVA_VSTEPMAX_OFFSET, .vlimitto = OMAP4_PRM_VP_IVA_VLIMITTO_OFFSET, .vstatus = OMAP4_PRM_VP_IVA_STATUS_OFFSET, .voltage = OMAP4_PRM_VP_IVA_VOLTAGE_OFFSET, - .prm_irqst_data = &omap4_vp_iva_prm_irqst_data, -}; - -static const struct omap_vp_prm_irqst_data omap4_vp_core_prm_irqst_data = { - .prm_irqst_reg = OMAP4_PRM_IRQSTATUS_MPU_OFFSET, - .tranxdone_status = OMAP4430_VP_CORE_TRANXDONE_ST_MASK, }; -struct omap_vp_instance_data omap4_vp_core_data = { - .vp_common = &omap4_vp_common, +struct omap_vp_instance omap4_vp_core = { + .id = OMAP4_VP_VDD_CORE_ID, + .common = &omap4_vp_common, .vpconfig = OMAP4_PRM_VP_CORE_CONFIG_OFFSET, .vstepmin = OMAP4_PRM_VP_CORE_VSTEPMIN_OFFSET, .vstepmax = OMAP4_PRM_VP_CORE_VSTEPMAX_OFFSET, .vlimitto = OMAP4_PRM_VP_CORE_VLIMITTO_OFFSET, .vstatus = OMAP4_PRM_VP_CORE_STATUS_OFFSET, .voltage = OMAP4_PRM_VP_CORE_VOLTAGE_OFFSET, - .prm_irqst_data = &omap4_vp_core_prm_irqst_data, }; - diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig index 6aafcfb8ed25..ba9daabb1c91 100644 --- a/arch/arm/plat-omap/Kconfig +++ b/arch/arm/plat-omap/Kconfig @@ -229,7 +229,7 @@ config OMAP_CARVEOUT_MEMPOOL_SIZE choice prompt "OMAP PM layer selection" depends on ARCH_OMAP - default OMAP_PM_NOOP + default OMAP_PM config OMAP_PM_NONE bool "No PM layer" @@ -237,6 +237,9 @@ config OMAP_PM_NONE config OMAP_PM_NOOP bool "No-op/debug PM layer" +config OMAP_PM + depends on PM + bool "OMAP PM layer implementation" endchoice endmenu diff --git a/arch/arm/plat-omap/Makefile b/arch/arm/plat-omap/Makefile index e78796b4d080..31fb1c3f62c3 100644 --- a/arch/arm/plat-omap/Makefile +++ b/arch/arm/plat-omap/Makefile @@ -22,7 +22,6 @@ obj-$(CONFIG_OMAP_RPMSG) += omap_rpmsg.o obj-$(CONFIG_OMAP_IOMMU) += iommu.o iovmm.o obj-$(CONFIG_OMAP_IOMMU_DEBUG) += iommu-debug.o -obj-$(CONFIG_CPU_FREQ) += cpu-omap.o obj-$(CONFIG_OMAP_DM_TIMER) += dmtimer.o obj-$(CONFIG_OMAP_DEBUG_DEVICES) += debug-devices.o obj-$(CONFIG_OMAP_DEBUG_LEDS) += debug-leds.o @@ -33,3 +32,4 @@ obj-y += $(i2c-omap-m) $(i2c-omap-y) obj-$(CONFIG_OMAP_MBOX_FWK) += mailbox.o obj-$(CONFIG_OMAP_PM_NOOP) += omap-pm-noop.o +obj-$(CONFIG_OMAP_PM) += omap-pm.o diff --git a/arch/arm/plat-omap/gpio.c b/arch/arm/plat-omap/gpio.c index d2adcdda23cf..c985652efd05 100644 --- a/arch/arm/plat-omap/gpio.c +++ b/arch/arm/plat-omap/gpio.c @@ -30,6 +30,8 @@ #include <mach/gpio.h> #include <asm/mach/irq.h> +#include <plat/omap_device.h> + /* * OMAP1510 GPIO registers */ @@ -1630,7 +1632,7 @@ static void omap_gpio_mod_init(struct gpio_bank *bank, int id) } } -static void __init omap_gpio_chip_init(struct gpio_bank *bank) +static void __devinit omap_gpio_chip_init(struct gpio_bank *bank) { int j; static int gpio; @@ -1861,6 +1863,7 @@ void omap2_gpio_prepare_for_idle(int off_mode) { int i, c = 0; int min = 0; + struct platform_device *pdev; if (cpu_is_omap34xx()) min = 1; @@ -1873,6 +1876,9 @@ void omap2_gpio_prepare_for_idle(int off_mode) for (j = 0; j < hweight_long(bank->dbck_enable_mask); j++) clk_disable(bank->dbck); + pdev = to_platform_device(bank->dev); + omap_device_idle(pdev); + if (!off_mode) continue; @@ -1930,6 +1936,7 @@ void omap2_gpio_resume_after_idle(void) { int i; int min = 0; + struct platform_device *pdev; if (cpu_is_omap34xx()) min = 1; @@ -1938,6 +1945,9 @@ void omap2_gpio_resume_after_idle(void) u32 l = 0, gen, gen0, gen1; int j; + pdev = to_platform_device(bank->dev); + omap_device_enable(pdev); + for (j = 0; j < hweight_long(bank->dbck_enable_mask); j++) clk_enable(bank->dbck); diff --git a/arch/arm/plat-omap/i2c.c b/arch/arm/plat-omap/i2c.c index 3341ca4703e9..5e76d4977611 100644 --- a/arch/arm/plat-omap/i2c.c +++ b/arch/arm/plat-omap/i2c.c @@ -120,7 +120,8 @@ static inline int omap1_i2c_add_bus(int bus_id) */ static void omap_pm_set_max_mpu_wakeup_lat_compat(struct device *dev, long t) { - omap_pm_set_max_mpu_wakeup_lat(dev, t); + static struct pm_qos_request_list *qos_request; + omap_pm_set_max_mpu_wakeup_lat(&qos_request, t); } static struct omap_device_pm_latency omap_i2c_latency[] = { @@ -158,7 +159,7 @@ static inline int omap2_i2c_add_bus(int bus_id) * completes. * Only omap3 has support for constraints */ - if (cpu_is_omap34xx()) + if (cpu_is_omap34xx() || cpu_is_omap44xx()) pdata->set_mpu_wkup_lat = omap_pm_set_max_mpu_wakeup_lat_compat; od = omap_device_build(name, bus_id, oh, pdata, sizeof(struct omap_i2c_bus_platform_data), diff --git a/arch/arm/plat-omap/include/plat/omap-pm.h b/arch/arm/plat-omap/include/plat/omap-pm.h index c0a752053039..1bfdf6381b98 100644 --- a/arch/arm/plat-omap/include/plat/omap-pm.h +++ b/arch/arm/plat-omap/include/plat/omap-pm.h @@ -17,7 +17,9 @@ #include <linux/device.h> #include <linux/cpufreq.h> #include <linux/clk.h> +#include <linux/pm_qos_params.h> #include <linux/opp.h> +#include <linux/pm_qos_params.h> /* * agent_id values for use with omap_pm_set_min_bus_tput(): @@ -73,7 +75,8 @@ void omap_pm_if_exit(void); /** * omap_pm_set_max_mpu_wakeup_lat - set the maximum MPU wakeup latency - * @dev: struct device * requesting the constraint + * @qos_request: handle for the constraint. The pointer should be + * initialized to NULL * @t: maximum MPU wakeup latency in microseconds * * Request that the maximum interrupt latency for the MPU to be no @@ -105,7 +108,8 @@ void omap_pm_if_exit(void); * Returns -EINVAL for an invalid argument, -ERANGE if the constraint * is not satisfiable, or 0 upon success. */ -int omap_pm_set_max_mpu_wakeup_lat(struct device *dev, long t); +int omap_pm_set_max_mpu_wakeup_lat(struct pm_qos_request_list **qos_request, + long t); /** @@ -132,12 +136,12 @@ int omap_pm_set_max_mpu_wakeup_lat(struct device *dev, long t); * * Multiple calls to omap_pm_set_min_bus_tput() will replace the * previous rate value for this device. To remove the interconnect - * throughput restriction for this device, call with r = 0. + * throughput restriction for this device, call with r = -1. * * Returns -EINVAL for an invalid argument, -ERANGE if the constraint * is not satisfiable, or 0 upon success. */ -int omap_pm_set_min_bus_tput(struct device *dev, u8 agent_id, unsigned long r); +int omap_pm_set_min_bus_tput(struct device *dev, u8 agent_id, long r); /** @@ -172,7 +176,8 @@ int omap_pm_set_max_dev_wakeup_lat(struct device *req_dev, struct device *dev, /** * omap_pm_set_max_sdma_lat - set the maximum system DMA transfer start latency - * @dev: struct device * + * @qos_request: handle for the constraint. The pointer should be + * initialized to NULL * @t: maximum DMA transfer start latency in microseconds * * Request that the maximum system DMA transfer start latency for this @@ -197,7 +202,8 @@ int omap_pm_set_max_dev_wakeup_lat(struct device *req_dev, struct device *dev, * Returns -EINVAL for an invalid argument, -ERANGE if the constraint * is not satisfiable, or 0 upon success. */ -int omap_pm_set_max_sdma_lat(struct device *dev, long t); +int omap_pm_set_max_sdma_lat(struct pm_qos_request_list **qos_request, + long t); /** @@ -350,9 +356,18 @@ unsigned long omap_pm_cpu_get_freq(void); * driver must restore device context. If the number of context losses * exceeds the maximum positive integer, the function will wrap to 0 and * continue counting. Returns the number of context losses for this device, - * or zero upon error. + * or -EINVAL upon error. + */ +int omap_pm_get_dev_context_loss_count(struct device *dev); + + +/** + * omap_pm_set_min_mpu_freq - sets the min frequency the mpu should be allowed + * to run. The function works with a granularity of 1000000. Any frequency requested, + * will set the mpu frequency to the closet higher frequency that can match the request. + * to release the constraint, the f parameter should be passed as -1. */ -u32 omap_pm_get_dev_context_loss_count(struct device *dev); +int omap_pm_set_min_mpu_freq(struct device *dev, unsigned long f); void omap_pm_enable_off_mode(void); void omap_pm_disable_off_mode(void); diff --git a/arch/arm/plat-omap/include/plat/omap-serial.h b/arch/arm/plat-omap/include/plat/omap-serial.h index 2682043f5a5b..71e238e1790c 100644 --- a/arch/arm/plat-omap/include/plat/omap-serial.h +++ b/arch/arm/plat-omap/include/plat/omap-serial.h @@ -112,5 +112,6 @@ struct uart_omap_port { char name[20]; unsigned long port_activity; }; +int omap_uart_active(int num, u32 timeout); #endif /* __OMAP_SERIAL_H__ */ diff --git a/arch/arm/plat-omap/include/plat/omap44xx.h b/arch/arm/plat-omap/include/plat/omap44xx.h index ea2b8a6306e7..c0d478e55c84 100644 --- a/arch/arm/plat-omap/include/plat/omap44xx.h +++ b/arch/arm/plat-omap/include/plat/omap44xx.h @@ -45,6 +45,7 @@ #define OMAP44XX_WKUPGEN_BASE 0x48281000 #define OMAP44XX_MCPDM_BASE 0x40132000 #define OMAP44XX_MCPDM_L3_BASE 0x49032000 +#define OMAP44XX_SAR_RAM_BASE 0x4a326000 #define OMAP44XX_MAILBOX_BASE (L4_44XX_BASE + 0xF4000) #define OMAP44XX_HSUSB_OTG_BASE (L4_44XX_BASE + 0xAB000) diff --git a/arch/arm/plat-omap/include/plat/omap_hwmod.h b/arch/arm/plat-omap/include/plat/omap_hwmod.h index 1adea9c62984..39d809adce7f 100644 --- a/arch/arm/plat-omap/include/plat/omap_hwmod.h +++ b/arch/arm/plat-omap/include/plat/omap_hwmod.h @@ -519,8 +519,6 @@ struct omap_hwmod { const char *main_clk; struct clk *_clk; struct omap_hwmod_opt_clk *opt_clks; - char *vdd_name; - struct voltagedomain *voltdm; struct omap_hwmod_ocp_if **masters; /* connect to *_IA */ struct omap_hwmod_ocp_if **slaves; /* connect to *_TA */ void *dev_attr; diff --git a/arch/arm/plat-omap/omap-pm-noop.c b/arch/arm/plat-omap/omap-pm-noop.c index b0471bb2d47d..614c80e68c77 100644 --- a/arch/arm/plat-omap/omap-pm-noop.c +++ b/arch/arm/plat-omap/omap-pm-noop.c @@ -33,19 +33,19 @@ static u32 dummy_context_loss_counter; * Device-driver-originated constraints (via board-*.c files) */ -int omap_pm_set_max_mpu_wakeup_lat(struct device *dev, long t) +int omap_pm_set_max_mpu_wakeup_lat(struct pm_qos_request_list **pmqos_req, + long t) { - if (!dev || t < -1) { + if (!pmqos_req || t < -1) { WARN(1, "OMAP PM: %s: invalid parameter(s)", __func__); return -EINVAL; }; if (t == -1) - pr_debug("OMAP PM: remove max MPU wakeup latency constraint: " - "dev %s\n", dev_name(dev)); + pr_debug("OMAP PM: remove max MPU wakeup latency constraint\n"); else - pr_debug("OMAP PM: add max MPU wakeup latency constraint: " - "dev %s, t = %ld usec\n", dev_name(dev), t); + pr_debug("OMAP PM: add max MPU wakeup latency constraint:" + "t = %ld usec\n", t); /* * For current Linux, this needs to map the MPU to a @@ -61,7 +61,7 @@ int omap_pm_set_max_mpu_wakeup_lat(struct device *dev, long t) return 0; } -int omap_pm_set_min_bus_tput(struct device *dev, u8 agent_id, unsigned long r) +int omap_pm_set_min_bus_tput(struct device *dev, u8 agent_id, long r) { if (!dev || (agent_id != OCP_INITIATOR_AGENT && agent_id != OCP_TARGET_AGENT)) { @@ -119,19 +119,18 @@ int omap_pm_set_max_dev_wakeup_lat(struct device *req_dev, struct device *dev, return 0; } -int omap_pm_set_max_sdma_lat(struct device *dev, long t) +int omap_pm_set_max_sdma_lat(struct pm_qos_request_list **qos_request, long t) { - if (!dev || t < -1) { + if (!qos_request || t < -1) { WARN(1, "OMAP PM: %s: invalid parameter(s)", __func__); return -EINVAL; }; if (t == -1) - pr_debug("OMAP PM: remove max DMA latency constraint: " - "dev %s\n", dev_name(dev)); + pr_debug("OMAP PM: remove max DMA latency constraint:\n"); else - pr_debug("OMAP PM: add max DMA latency constraint: " - "dev %s, t = %ld usec\n", dev_name(dev), t); + pr_debug("OMAP PM: add max DMA latency constraint:" + "t = %ld usec\n", t); /* * For current Linux PM QOS params, this code should scan the @@ -311,7 +310,7 @@ void omap_pm_disable_off_mode(void) #ifdef CONFIG_ARCH_OMAP2PLUS -u32 omap_pm_get_dev_context_loss_count(struct device *dev) +int omap_pm_get_dev_context_loss_count(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); u32 count; diff --git a/arch/arm/plat-omap/omap-pm.c b/arch/arm/plat-omap/omap-pm.c new file mode 100644 index 000000000000..a2ee551a5f0f --- /dev/null +++ b/arch/arm/plat-omap/omap-pm.c @@ -0,0 +1,630 @@ +/* + * omap-pm.c - OMAP power management interface + * + * Copyright (C) 2008-2010 Texas Instruments, Inc. + * Copyright (C) 2008-2009 Nokia Corporation + * Vishwanath BS + * + * This code is based on plat-omap/omap-pm-noop.c. + * + * Interface developed by (in alphabetical order): + * Karthik Dasu, Tony Lindgren, Rajendra Nayak, Sakari Poussa, Veeramanikandan + * Raju, Anand Sawant, Igor Stoppa, Paul Walmsley, Richard Woodruff + */ + +#undef DEBUG + +#include <linux/init.h> +#include <linux/cpufreq.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/list.h> + +/* Interface documentation is in mach/omap-pm.h */ +#include <plat/omap-pm.h> +#include <plat/omap_device.h> +#include <plat/common.h> +#include "../mach-omap2/powerdomain.h" + +struct omap_opp *dsp_opps; +struct omap_opp *mpu_opps; +struct omap_opp *l3_opps; + +static DEFINE_MUTEX(bus_tput_mutex); +static DEFINE_MUTEX(mpu_tput_mutex); +static DEFINE_MUTEX(mpu_lat_mutex); + +static bool off_mode_enabled; + +/* Used to model a Interconnect Throughput */ +static struct interconnect_tput { + /* Total no of users at any point of interconnect */ + u8 no_of_users; + /* List of all the current users for interconnect */ + struct list_head users_list; + struct list_head node; + /* Protect interconnect throughput */ + struct mutex throughput_mutex; + /* Target level for interconnect throughput */ + unsigned long target_level; + +} *bus_tput; + +/* Used to represent a user of a interconnect throughput */ +struct users { + /* Device pointer used to uniquely identify the user */ + struct device *dev; + struct list_head node; + /* Current level as requested for interconnect throughput by the user */ + u32 level; +}; + +/* Private/Internal Functions */ + +/** + * user_lookup - look up a user by its device pointer, return a pointer + * @dev: The device to be looked up + * + * Looks for a interconnect user by its device pointer. Returns a + * pointer to + * the struct users if found, else returns NULL. + **/ + +static struct users *user_lookup(struct device *dev) +{ + struct users *usr, *tmp_usr; + + usr = NULL; + list_for_each_entry(tmp_usr, &bus_tput->users_list, node) { + if (tmp_usr->dev == dev) { + usr = tmp_usr; + break; + } + } + + return usr; +} + +/** + * get_user - gets a new users_list struct dynamically + * + * This function allocates dynamcially the user node + * Returns a pointer to users struct on success. On dynamic allocation + * failure + * returns a ERR_PTR(-ENOMEM). + **/ + +static struct users *get_user(void) +{ + struct users *user; + + user = kmalloc(sizeof(struct users), GFP_KERNEL); + if (!user) { + pr_err("%s FATAL ERROR: kmalloc " + "failed\n", __func__); + return ERR_PTR(-ENOMEM); + } + return user; +} + + +/** + * omap_bus_tput_init - Initializes the interconnect throughput + * userlist + * Allocates memory for global throughput variable dynamically. + * Intializes Userlist, no. of users and throughput target level. + * Returns 0 on sucess, else returns EINVAL if memory + * allocation fails. + */ +int omap_bus_tput_init(void) +{ + bus_tput = kmalloc(sizeof(struct interconnect_tput), GFP_KERNEL); + if (!bus_tput) { + pr_err("%s FATAL ERROR: kmalloc failed\n", __func__); + return -EINVAL; + } + INIT_LIST_HEAD(&bus_tput->users_list); + mutex_init(&bus_tput->throughput_mutex); + bus_tput->no_of_users = 0; + bus_tput->target_level = 0; + return 0; +} + + +/** + * add_req_tput - Request for a required level by a device + * @dev: Uniquely identifes the caller + * @level: The requested level for the interconnect bandwidth in KiB/s + * + * This function recomputes the target level of the interconnect + * bandwidth + * based on the level requested by all the users. + * Multiple calls to this function by the same device will + * replace the previous level requested + * Returns the updated level of interconnect throughput. + * In case of Invalid dev or user pointer, it returns 0. + */ +static unsigned long add_req_tput(struct device *dev, unsigned long level) +{ + int ret; + struct users *user; + + if (!dev) { + pr_err("Invalid dev pointer\n"); + ret = 0; + } + mutex_lock(&bus_tput->throughput_mutex); + user = user_lookup(dev); + if (user == NULL) { + user = get_user(); + if (IS_ERR(user)) { + pr_err("Couldn't get user from the list to" + "add new throughput constraint"); + ret = 0; + goto unlock; + } + bus_tput->target_level += level; + bus_tput->no_of_users++; + user->dev = dev; + list_add(&user->node, &bus_tput->users_list); + user->level = level; + } else { + bus_tput->target_level -= user->level; + bus_tput->target_level += level; + user->level = level; + } + ret = bus_tput->target_level; +unlock: + mutex_unlock(&bus_tput->throughput_mutex); + return ret; +} + + +/** + * remove_req_tput - Release a previously requested level of + * a throughput level for interconnect + * @dev: Device pointer to dev + * + * This function recomputes the target level of the interconnect + * throughput after removing + * the level requested by the user. + * Returns 0, if the dev structure is invalid + * else returns modified interconnect throughput rate. + */ +static unsigned long remove_req_tput(struct device *dev) +{ + struct users *user; + int found = 0; + int ret; + + mutex_lock(&bus_tput->throughput_mutex); + list_for_each_entry(user, &bus_tput->users_list, node) { + if (user->dev == dev) { + found = 1; + break; + } + } + if (!found) { + /* No such user exists */ + pr_err("Invalid Device Structure\n"); + ret = 0; + goto unlock; + } + bus_tput->target_level -= user->level; + bus_tput->no_of_users--; + list_del(&user->node); + kfree(user); + ret = bus_tput->target_level; +unlock: + mutex_unlock(&bus_tput->throughput_mutex); + return ret; +} + +/* + * Device-driver-originated constraints (via board-*.c files) + */ + +int omap_pm_set_max_mpu_wakeup_lat(struct pm_qos_request_list **qos_request, + long t) +{ + if (!qos_request || t < -1) { + WARN(1, "OMAP PM: %s: invalid parameter(s)", __func__); + return -EINVAL; + }; + + mutex_lock(&mpu_lat_mutex); + + if (t == -1) { + pm_qos_remove_request(*qos_request); + kfree(*qos_request); + *qos_request = NULL; + } else if (*qos_request == NULL) { + *qos_request = kzalloc(sizeof(struct pm_qos_request_list), GFP_KERNEL); + pm_qos_add_request(*qos_request, PM_QOS_CPU_DMA_LATENCY, t); + } else + pm_qos_update_request(*qos_request, t); + + mutex_unlock(&mpu_lat_mutex); + return 0; +} + + +int omap_pm_set_min_bus_tput(struct device *dev, u8 agent_id, long r) +{ + + int ret; + struct device *l3_dev; + static struct device dummy_l3_dev; + unsigned long target_level = 0; + + if (!dev || (agent_id != OCP_INITIATOR_AGENT && + agent_id != OCP_TARGET_AGENT)) { + WARN(1, "OMAP PM: %s: invalid parameter(s)", __func__); + return -EINVAL; + }; + + mutex_lock(&bus_tput_mutex); + + l3_dev = omap2_get_l3_device(); + if (!l3_dev) { + pr_err("Unable to get l3 device pointer"); + ret = -EINVAL; + goto unlock; + } + if (r == -1) { + pr_debug("OMAP PM: remove min bus tput constraint for: " + "interconnect dev %s for agent_id %d\n", dev_name(dev), + agent_id); + target_level = remove_req_tput(dev); + } else { + pr_debug("OMAP PM: add min bus tput constraint for: " + "interconnect dev %s for agent_id %d: rate %ld KiB\n", + dev_name(dev), agent_id, r); + target_level = add_req_tput(dev, r); + } + + /* Convert the throughput(in KiB/s) into Hz. */ + target_level = (target_level * 1000)/4; + + WARN(1, "OMAP PM: %s: constraint not called, needs DVFS", __func__); +#if 0 + ret = omap_device_scale(&dummy_l3_dev, l3_dev, target_level); +#endif + if (ret) + pr_err("Unable to change level for interconnect bandwidth to %ld\n", + target_level); +unlock: + mutex_unlock(&bus_tput_mutex); + return ret; +} + +int omap_pm_set_max_dev_wakeup_lat(struct device *req_dev, struct device *dev, + long t) +{ + struct omap_device *odev; + struct powerdomain *pwrdm_dev; + struct platform_device *pdev; + int ret = 0; + + if (!req_dev || !dev || t < -1) { + WARN(1, "OMAP PM: %s: invalid parameter(s)", __func__); + return -EINVAL; + }; + + /* Look for the devices Power Domain */ + pdev = container_of(dev, struct platform_device, dev); + + /* Try to catch non platform devices. */ + if (pdev->name == NULL) { + pr_err("OMAP-PM: Error: platform device not valid\n"); + return -EINVAL; + } + + odev = to_omap_device(pdev); + if (odev) { + pwrdm_dev = omap_device_get_pwrdm(odev); + } else { + pr_err("OMAP-PM: Error: Could not find omap_device " + "for %s\n", pdev->name); + return -EINVAL; + } + + /* Catch devices with undefined powerdomains. */ + if (!pwrdm_dev) { + pr_err("OMAP-PM: Error: could not find parent " + "powerdomain for %s\n", pdev->name); + return -EINVAL; + } + + if (t == -1) { + pr_debug("OMAP PM: remove max device latency constraint: " + "dev %s, pwrdm %s, req by %s\n", dev_name(dev), + pwrdm_dev->name, dev_name(req_dev)); + ret = pwrdm_wakeuplat_release_constraint(pwrdm_dev, req_dev); + } else { + pr_debug("OMAP PM: add max device latency constraint: " + "dev %s, t = %ld usec, pwrdm %s, req by %s\n", + dev_name(dev), t, pwrdm_dev->name, dev_name(req_dev)); + ret = pwrdm_wakeuplat_set_constraint(pwrdm_dev, req_dev, t); + } + + /* + * For current Linux, this needs to map the device to a + * powerdomain, then go through the list of current max lat + * constraints on that powerdomain and find the smallest. If + * the latency constraint has changed, the code should + * recompute the state to enter for the next powerdomain + * state. Conceivably, this code should also determine + * whether to actually disable the device clocks or not, + * depending on how long it takes to re-enable the clocks. + * + * TI CDP code can call constraint_set here. + */ + + return ret; +} + +int omap_pm_set_max_sdma_lat(struct pm_qos_request_list **qos_request, + long t) +{ + if (!qos_request || t < -1) { + WARN(1, "OMAP PM: %s: invalid parameter(s)", __func__); + return -EINVAL; + }; + + if (t == -1) { + pm_qos_remove_request(*qos_request); + kfree(*qos_request); + *qos_request = NULL; + } else if (*qos_request == NULL) { + *qos_request = kzalloc(sizeof(struct pm_qos_request_list), GFP_KERNEL); + pm_qos_add_request(*qos_request, PM_QOS_CPU_DMA_LATENCY, t); + } else + pm_qos_update_request(*qos_request, t); + + return 0; +} + +int omap_pm_set_min_clk_rate(struct device *dev, struct clk *c, long r) +{ + if (!dev || !c || r < 0) { + WARN(1, "OMAP PM: %s: invalid parameter(s)", __func__); + return -EINVAL; + } + + if (r == 0) + pr_debug("OMAP PM: remove min clk rate constraint: " + "dev %s\n", dev_name(dev)); + else + pr_debug("OMAP PM: add min clk rate constraint: " + "dev %s, rate = %ld Hz\n", dev_name(dev), r); + + /* + * Code in a real implementation should keep track of these + * constraints on the clock, and determine the highest minimum + * clock rate. It should iterate over each OPP and determine + * whether the OPP will result in a clock rate that would + * satisfy this constraint (and any other PM constraint in effect + * at that time). Once it finds the lowest-voltage OPP that + * meets those conditions, it should switch to it, or return + * an error if the code is not capable of doing so. + */ + + return 0; +} + +/* + * DSP Bridge-specific constraints + */ + +const struct omap_opp *omap_pm_dsp_get_opp_table(void) +{ + pr_debug("OMAP PM: DSP request for OPP table\n"); + + /* + * Return DSP frequency table here: The final item in the + * array should have .rate = .opp_id = 0. + */ + + return NULL; +} + +void omap_pm_dsp_set_min_opp(u8 opp_id) +{ + if (opp_id == 0) { + WARN_ON(1); + return; + } + + pr_debug("OMAP PM: DSP requests minimum VDD1 OPP to be %d\n", opp_id); + + /* + * + * For l-o dev tree, our VDD1 clk is keyed on OPP ID, so we + * can just test to see which is higher, the CPU's desired OPP + * ID or the DSP's desired OPP ID, and use whichever is + * highest. + * + * In CDP12.14+, the VDD1 OPP custom clock that controls the DSP + * rate is keyed on MPU speed, not the OPP ID. So we need to + * map the OPP ID to the MPU speed for use with clk_set_rate() + * if it is higher than the current OPP clock rate. + * + */ +} + + +u8 omap_pm_dsp_get_opp(void) +{ + pr_debug("OMAP PM: DSP requests current DSP OPP ID\n"); + + /* + * For l-o dev tree, call clk_get_rate() on VDD1 OPP clock + * + * CDP12.14+: + * Call clk_get_rate() on the OPP custom clock, map that to an + * OPP ID using the tables defined in board-*.c/chip-*.c files. + */ + + return 0; +} + +/* + * CPUFreq-originated constraint + * + * In the future, this should be handled by custom OPP clocktype + * functions. + */ + +struct cpufreq_frequency_table **omap_pm_cpu_get_freq_table(void) +{ + pr_debug("OMAP PM: CPUFreq request for frequency table\n"); + + /* + * Return CPUFreq frequency table here: loop over + * all VDD1 clkrates, pull out the mpu_ck frequencies, build + * table + */ + + return NULL; +} + +void omap_pm_cpu_set_freq(unsigned long f) +{ + if (f == 0) { + WARN_ON(1); + return; + } + + pr_debug("OMAP PM: CPUFreq requests CPU frequency to be set to %lu\n", + f); + + /* + * For l-o dev tree, determine whether MPU freq or DSP OPP id + * freq is higher. Find the OPP ID corresponding to the + * higher frequency. Call clk_round_rate() and clk_set_rate() + * on the OPP custom clock. + * + * CDP should just be able to set the VDD1 OPP clock rate here. + */ +} + +unsigned long omap_pm_cpu_get_freq(void) +{ + pr_debug("OMAP PM: CPUFreq requests current CPU frequency\n"); + + /* + * Call clk_get_rate() on the mpu_ck. + */ + + return 0; +} + +/** + * omap_pm_enable_off_mode - notify OMAP PM that off-mode is enabled + * + * Intended for use only by OMAP PM core code to notify this layer + * that off mode has been enabled. + */ +void omap_pm_enable_off_mode(void) +{ + off_mode_enabled = true; +} + +/** + * omap_pm_disable_off_mode - notify OMAP PM that off-mode is disabled + * + * Intended for use only by OMAP PM core code to notify this layer + * that off mode has been disabled. + */ +void omap_pm_disable_off_mode(void) +{ + off_mode_enabled = false; +} + +/* + * Device context loss tracking + */ + +int omap_pm_get_dev_context_loss_count(struct device *dev) +{ + static u32 counter = 1; + + if (!dev) { + WARN_ON(1); + return -EINVAL; + }; + + pr_debug("OMAP PM: returning context loss count for dev %s\n", + dev_name(dev)); + + /* + * Map the device to the powerdomain. Return the powerdomain + * off counter. + */ + + /* Let the counter roll-over: its for test only */ + return counter++; +} + + +/* Should be called before clk framework init */ +int __init omap_pm_if_early_init() +{ + return 0; +} + +/* Must be called after clock framework is initialized */ +int __init omap_pm_if_init(void) +{ + int ret; + ret = omap_bus_tput_init(); + if (ret) + pr_err("Failed to initialize interconnect" + " bandwidth users list\n"); + return ret; +} + +void omap_pm_if_exit(void) +{ + /* Deallocate CPUFreq frequency table here */ +} + +int omap_pm_set_min_mpu_freq(struct device *dev, unsigned long f) +{ + + int ret = 0; + struct device *mpu_dev; + + if (!dev) { + WARN(1, "OMAP PM: %s: invalid parameter(s)", __func__); + return -EINVAL; + } + + mutex_lock(&mpu_tput_mutex); + + mpu_dev = omap2_get_mpuss_device(); + if (!mpu_dev) { + pr_err("Unable to get MPU device pointer"); + ret = -EINVAL; + goto unlock; + } + + + /* Rescale the frequency if a change is detected with + * the new constraint. + */ + WARN(1, "OMAP PM: %s: constraint not called, needs DVFS", __func__); +#if 0 + ret = omap_device_set_rate(dev, mpu_dev, f); +#endif + if (ret) + pr_err("Unable to set MPU frequency to %ld\n", f); + +unlock: + mutex_unlock(&mpu_tput_mutex); + return ret; +} +EXPORT_SYMBOL(omap_pm_set_min_mpu_freq); diff --git a/arch/arm/plat-omap/omap_device.c b/arch/arm/plat-omap/omap_device.c index 9bbda9acb73b..f7c6dcaf928a 100644 --- a/arch/arm/plat-omap/omap_device.c +++ b/arch/arm/plat-omap/omap_device.c @@ -145,12 +145,12 @@ static int _omap_device_activate(struct omap_device *od, u8 ignore_lat) odpl->activate_lat_worst = act_lat; if (odpl->flags & OMAP_DEVICE_LATENCY_AUTO_ADJUST) { odpl->activate_lat = act_lat; - pr_warning("omap_device: %s.%d: new worst case " + pr_debug("omap_device: %s.%d: new worst case " "activate latency %d: %llu\n", od->pdev.name, od->pdev.id, od->pm_lat_level, act_lat); } else - pr_warning("omap_device: %s.%d: activate " + pr_debug("omap_device: %s.%d: activate " "latency %d higher than exptected. " "(%llu > %d)\n", od->pdev.name, od->pdev.id, @@ -213,12 +213,12 @@ static int _omap_device_deactivate(struct omap_device *od, u8 ignore_lat) odpl->deactivate_lat_worst = deact_lat; if (odpl->flags & OMAP_DEVICE_LATENCY_AUTO_ADJUST) { odpl->deactivate_lat = deact_lat; - pr_warning("omap_device: %s.%d: new worst case " + pr_debug("omap_device: %s.%d: new worst case " "deactivate latency %d: %llu\n", od->pdev.name, od->pdev.id, od->pm_lat_level, deact_lat); } else - pr_warning("omap_device: %s.%d: deactivate " + pr_debug("omap_device: %s.%d: deactivate " "latency %d higher than exptected. " "(%llu > %d)\n", od->pdev.name, od->pdev.id, diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 56a6899f5e9e..5cc12322ef32 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -625,4 +625,21 @@ int opp_init_cpufreq_table(struct device *dev, return 0; } + +/** + * opp_free_cpufreq_table() - free the cpufreq table + * @dev: device for which we do this operation + * @table: table to free + * + * Free up the table allocated by opp_init_cpufreq_table + */ +void opp_free_cpufreq_table(struct device *dev, + struct cpufreq_frequency_table **table) +{ + if (!table) + return; + + kfree(*table); + *table = NULL; +} #endif /* CONFIG_CPU_FREQ */ diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index ca8ee8093d6c..c716a0e50f2c 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -110,6 +110,19 @@ config CPU_FREQ_DEFAULT_GOV_CONSERVATIVE Be aware that not all cpufreq drivers support the conservative governor. If unsure have a look at the help section of the driver. Fallback governor will be the performance governor. + +config CPU_FREQ_DEFAULT_GOV_HOTPLUG + bool "hotplug" + select CPU_FREQ_GOV_HOTPLUG + select CPU_FREQ_GOV_PERFORMANCE + help + Use the CPUFreq governor 'hotplug' as default. This allows you + to get a full dynamic frequency capable system with CPU + hotplug support by simply loading your cpufreq low-level + hardware driver. Be aware that not all cpufreq drivers + support the hotplug governor. If unsure have a look at + the help section of the driver. Fallback governor will be the + performance governor. endchoice config CPU_FREQ_GOV_PERFORMANCE @@ -190,4 +203,24 @@ config CPU_FREQ_GOV_CONSERVATIVE If in doubt, say N. +config CPU_FREQ_GOV_HOTPLUG + tristate "'hotplug' cpufreq governor" + depends on CPU_FREQ && NO_HZ && HOTPLUG_CPU + help + 'hotplug' - this driver mimics the frequency scaling behavior + in 'ondemand', but with several key differences. First is + that frequency transitions use the CPUFreq table directly, + instead of incrementing in a percentage of the maximum + available frequency. Second 'hotplug' will offline auxillary + CPUs when the system is idle, and online those CPUs once the + system becomes busy again. This last feature is needed for + architectures which transition to low power states when only + the "master" CPU is online, or for thermally constrained + devices. + + If you don't have one of these architectures or devices, use + 'ondemand' instead. + + If in doubt, say N. + endif # CPU_FREQ diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 71fc3b4173f1..05d564c8a122 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_POWERSAVE) += cpufreq_powersave.o obj-$(CONFIG_CPU_FREQ_GOV_USERSPACE) += cpufreq_userspace.o obj-$(CONFIG_CPU_FREQ_GOV_ONDEMAND) += cpufreq_ondemand.o obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o +obj-$(CONFIG_CPU_FREQ_GOV_HOTPLUG) += cpufreq_hotplug.o # CPUfreq cross-arch helpers obj-$(CONFIG_CPU_FREQ_TABLE) += freq_table.o diff --git a/drivers/cpufreq/cpufreq_hotplug.c b/drivers/cpufreq/cpufreq_hotplug.c new file mode 100644 index 000000000000..85aa6d2c2e4a --- /dev/null +++ b/drivers/cpufreq/cpufreq_hotplug.c @@ -0,0 +1,705 @@ +/* + * CPUFreq hotplug governor + * + * Copyright (C) 2010 Texas Instruments, Inc. + * Mike Turquette <mturquette@ti.com> + * Santosh Shilimkar <santosh.shilimkar@ti.com> + * + * Based on ondemand governor + * Copyright (C) 2001 Russell King + * (C) 2003 Venkatesh Pallipadi <venkatesh.pallipadi@intel.com>, + * Jun Nakajima <jun.nakajima@intel.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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/cpufreq.h> +#include <linux/cpu.h> +#include <linux/jiffies.h> +#include <linux/kernel_stat.h> +#include <linux/mutex.h> +#include <linux/hrtimer.h> +#include <linux/tick.h> +#include <linux/ktime.h> +#include <linux/sched.h> +#include <linux/err.h> +#include <linux/slab.h> + +/* greater than 80% avg load across online CPUs increases frequency */ +#define DEFAULT_UP_FREQ_MIN_LOAD (80) + +/* less than 20% avg load across online CPUs decreases frequency */ +#define DEFAULT_DOWN_FREQ_MAX_LOAD (20) + +/* default sampling period (uSec) is bogus; 10x ondemand's default for x86 */ +#define DEFAULT_SAMPLING_PERIOD (100000) + +/* default number of sampling periods to average before hotplug-in decision */ +#define DEFAULT_HOTPLUG_IN_SAMPLING_PERIODS (5) + +/* default number of sampling periods to average before hotplug-out decision */ +#define DEFAULT_HOTPLUG_OUT_SAMPLING_PERIODS (20) + +static void do_dbs_timer(struct work_struct *work); +static int cpufreq_governor_dbs(struct cpufreq_policy *policy, + unsigned int event); + +#ifndef CONFIG_CPU_FREQ_DEFAULT_GOV_HOTPLUG +static +#endif +struct cpufreq_governor cpufreq_gov_hotplug = { + .name = "hotplug", + .governor = cpufreq_governor_dbs, + .owner = THIS_MODULE, +}; + +struct cpu_dbs_info_s { + cputime64_t prev_cpu_idle; + cputime64_t prev_cpu_wall; + cputime64_t prev_cpu_nice; + struct cpufreq_policy *cur_policy; + struct delayed_work work; + struct cpufreq_frequency_table *freq_table; + int cpu; + /* + * percpu mutex that serializes governor limit change with + * do_dbs_timer invocation. We do not want do_dbs_timer to run + * when user is changing the governor or limits. + */ + struct mutex timer_mutex; +}; +static DEFINE_PER_CPU(struct cpu_dbs_info_s, hp_cpu_dbs_info); + +static unsigned int dbs_enable; /* number of CPUs using this policy */ + +/* + * dbs_mutex protects data in dbs_tuners_ins from concurrent changes on + * different CPUs. It protects dbs_enable in governor start/stop. + */ +static DEFINE_MUTEX(dbs_mutex); + +static struct workqueue_struct *khotplug_wq; + +static struct dbs_tuners { + unsigned int sampling_rate; + unsigned int up_threshold; + unsigned int down_threshold; + unsigned int hotplug_in_sampling_periods; + unsigned int hotplug_out_sampling_periods; + unsigned int hotplug_load_index; + unsigned int *hotplug_load_history; + unsigned int ignore_nice; + unsigned int io_is_busy; +} dbs_tuners_ins = { + .sampling_rate = DEFAULT_SAMPLING_PERIOD, + .up_threshold = DEFAULT_UP_FREQ_MIN_LOAD, + .down_threshold = DEFAULT_DOWN_FREQ_MAX_LOAD, + .hotplug_in_sampling_periods = DEFAULT_HOTPLUG_IN_SAMPLING_PERIODS, + .hotplug_out_sampling_periods = DEFAULT_HOTPLUG_OUT_SAMPLING_PERIODS, + .hotplug_load_index = 0, + .ignore_nice = 0, + .io_is_busy = 0, +}; + +/* + * A corner case exists when switching io_is_busy at run-time: comparing idle + * times from a non-io_is_busy period to an io_is_busy period (or vice-versa) + * will misrepresent the actual change in system idleness. We ignore this + * corner case: enabling io_is_busy might cause freq increase and disabling + * might cause freq decrease, which probably matches the original intent. + */ +static inline cputime64_t get_cpu_idle_time(unsigned int cpu, cputime64_t *wall) +{ + u64 idle_time; + u64 iowait_time; + + /* cpufreq-hotplug always assumes CONFIG_NO_HZ */ + idle_time = get_cpu_idle_time_us(cpu, wall); + + /* add time spent doing I/O to idle time */ + if (dbs_tuners_ins.io_is_busy) { + iowait_time = get_cpu_iowait_time_us(cpu, wall); + /* cpufreq-hotplug always assumes CONFIG_NO_HZ */ + if (iowait_time != -1ULL && idle_time >= iowait_time) + idle_time -= iowait_time; + } + + return idle_time; +} + +/************************** sysfs interface ************************/ + +/* XXX look at global sysfs macros in cpufreq.h, can those be used here? */ + +/* cpufreq_hotplug Governor Tunables */ +#define show_one(file_name, object) \ +static ssize_t show_##file_name \ +(struct kobject *kobj, struct attribute *attr, char *buf) \ +{ \ + return sprintf(buf, "%u\n", dbs_tuners_ins.object); \ +} +show_one(sampling_rate, sampling_rate); +show_one(up_threshold, up_threshold); +show_one(down_threshold, down_threshold); +show_one(hotplug_in_sampling_periods, hotplug_in_sampling_periods); +show_one(hotplug_out_sampling_periods, hotplug_out_sampling_periods); +show_one(ignore_nice_load, ignore_nice); +show_one(io_is_busy, io_is_busy); + +static ssize_t store_sampling_rate(struct kobject *a, struct attribute *b, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; + + mutex_lock(&dbs_mutex); + dbs_tuners_ins.sampling_rate = input; + mutex_unlock(&dbs_mutex); + + return count; +} + +static ssize_t store_up_threshold(struct kobject *a, struct attribute *b, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + + if (ret != 1 || input <= dbs_tuners_ins.down_threshold) { + return -EINVAL; + } + + mutex_lock(&dbs_mutex); + dbs_tuners_ins.up_threshold = input; + mutex_unlock(&dbs_mutex); + + return count; +} + +static ssize_t store_down_threshold(struct kobject *a, struct attribute *b, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + ret = sscanf(buf, "%u", &input); + + if (ret != 1 || input >= dbs_tuners_ins.up_threshold) { + return -EINVAL; + } + + mutex_lock(&dbs_mutex); + dbs_tuners_ins.down_threshold = input; + mutex_unlock(&dbs_mutex); + + return count; +} + +static ssize_t store_hotplug_in_sampling_periods(struct kobject *a, + struct attribute *b, const char *buf, size_t count) +{ + unsigned int input; + unsigned int *temp; + unsigned int max_windows; + int ret; + ret = sscanf(buf, "%u", &input); + + if (ret != 1) + return -EINVAL; + + /* already using this value, bail out */ + if (input == dbs_tuners_ins.hotplug_in_sampling_periods) + return count; + + mutex_lock(&dbs_mutex); + ret = count; + max_windows = max(dbs_tuners_ins.hotplug_in_sampling_periods, + dbs_tuners_ins.hotplug_out_sampling_periods); + + /* no need to resize array */ + if (input <= max_windows) { + dbs_tuners_ins.hotplug_in_sampling_periods = input; + goto out; + } + + /* resize array */ + temp = kmalloc((sizeof(unsigned int) * input), GFP_KERNEL); + + if (!temp || IS_ERR(temp)) { + ret = -ENOMEM; + goto out; + } + + memcpy(temp, dbs_tuners_ins.hotplug_load_history, + (max_windows * sizeof(unsigned int))); + kfree(dbs_tuners_ins.hotplug_load_history); + + /* replace old buffer, old number of sampling periods & old index */ + dbs_tuners_ins.hotplug_load_history = temp; + dbs_tuners_ins.hotplug_in_sampling_periods = input; + dbs_tuners_ins.hotplug_load_index = max_windows; +out: + mutex_unlock(&dbs_mutex); + + return ret; +} + +static ssize_t store_hotplug_out_sampling_periods(struct kobject *a, + struct attribute *b, const char *buf, size_t count) +{ + unsigned int input; + unsigned int *temp; + unsigned int max_windows; + int ret; + ret = sscanf(buf, "%u", &input); + + if (ret != 1) + return -EINVAL; + + /* already using this value, bail out */ + if (input == dbs_tuners_ins.hotplug_out_sampling_periods) + return count; + + mutex_lock(&dbs_mutex); + ret = count; + max_windows = max(dbs_tuners_ins.hotplug_in_sampling_periods, + dbs_tuners_ins.hotplug_out_sampling_periods); + + /* no need to resize array */ + if (input <= max_windows) { + dbs_tuners_ins.hotplug_out_sampling_periods = input; + goto out; + } + + /* resize array */ + temp = kmalloc((sizeof(unsigned int) * input), GFP_KERNEL); + + if (!temp || IS_ERR(temp)) { + ret = -ENOMEM; + goto out; + } + + memcpy(temp, dbs_tuners_ins.hotplug_load_history, + (max_windows * sizeof(unsigned int))); + kfree(dbs_tuners_ins.hotplug_load_history); + + /* replace old buffer, old number of sampling periods & old index */ + dbs_tuners_ins.hotplug_load_history = temp; + dbs_tuners_ins.hotplug_out_sampling_periods = input; + dbs_tuners_ins.hotplug_load_index = max_windows; +out: + mutex_unlock(&dbs_mutex); + + return ret; +} + +static ssize_t store_ignore_nice_load(struct kobject *a, struct attribute *b, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + + unsigned int j; + + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; + + if (input > 1) + input = 1; + + mutex_lock(&dbs_mutex); + if (input == dbs_tuners_ins.ignore_nice) { /* nothing to do */ + mutex_unlock(&dbs_mutex); + return count; + } + dbs_tuners_ins.ignore_nice = input; + + /* we need to re-evaluate prev_cpu_idle */ + for_each_online_cpu(j) { + struct cpu_dbs_info_s *dbs_info; + dbs_info = &per_cpu(hp_cpu_dbs_info, j); + dbs_info->prev_cpu_idle = get_cpu_idle_time(j, + &dbs_info->prev_cpu_wall); + if (dbs_tuners_ins.ignore_nice) + dbs_info->prev_cpu_nice = kstat_cpu(j).cpustat.nice; + + } + mutex_unlock(&dbs_mutex); + + return count; +} + +static ssize_t store_io_is_busy(struct kobject *a, struct attribute *b, + const char *buf, size_t count) +{ + unsigned int input; + int ret; + + ret = sscanf(buf, "%u", &input); + if (ret != 1) + return -EINVAL; + + mutex_lock(&dbs_mutex); + dbs_tuners_ins.io_is_busy = !!input; + mutex_unlock(&dbs_mutex); + + return count; +} + +define_one_global_rw(sampling_rate); +define_one_global_rw(up_threshold); +define_one_global_rw(down_threshold); +define_one_global_rw(hotplug_in_sampling_periods); +define_one_global_rw(hotplug_out_sampling_periods); +define_one_global_rw(ignore_nice_load); +define_one_global_rw(io_is_busy); + +static struct attribute *dbs_attributes[] = { + &sampling_rate.attr, + &up_threshold.attr, + &down_threshold.attr, + &hotplug_in_sampling_periods.attr, + &hotplug_out_sampling_periods.attr, + &ignore_nice_load.attr, + &io_is_busy.attr, + NULL +}; + +static struct attribute_group dbs_attr_group = { + .attrs = dbs_attributes, + .name = "hotplug", +}; + +/************************** sysfs end ************************/ + +static void dbs_check_cpu(struct cpu_dbs_info_s *this_dbs_info) +{ + /* combined load of all enabled CPUs */ + unsigned int total_load = 0; + /* single largest CPU load */ + unsigned int max_load = 0; + /* average load across all enabled CPUs */ + unsigned int avg_load = 0; + /* average load across multiple sampling periods for hotplug events */ + unsigned int hotplug_in_avg_load = 0; + unsigned int hotplug_out_avg_load = 0; + /* number of sampling periods averaged for hotplug decisions */ + unsigned int periods; + + struct cpufreq_policy *policy; + unsigned int index = 0; + unsigned int i, j; + + policy = this_dbs_info->cur_policy; + + /* + * cpu load accounting + * get highest load, total load and average load across all CPUs + */ + for_each_cpu(j, policy->cpus) { + unsigned int load; + unsigned int idle_time, wall_time; + cputime64_t cur_wall_time, cur_idle_time; + struct cpu_dbs_info_s *j_dbs_info; + + j_dbs_info = &per_cpu(hp_cpu_dbs_info, j); + + /* update both cur_idle_time and cur_wall_time */ + cur_idle_time = get_cpu_idle_time(j, &cur_wall_time); + + /* how much wall time has passed since last iteration? */ + wall_time = (unsigned int) cputime64_sub(cur_wall_time, + j_dbs_info->prev_cpu_wall); + j_dbs_info->prev_cpu_wall = cur_wall_time; + + /* how much idle time has passed since last iteration? */ + idle_time = (unsigned int) cputime64_sub(cur_idle_time, + j_dbs_info->prev_cpu_idle); + j_dbs_info->prev_cpu_idle = cur_idle_time; + + if (unlikely(!wall_time || wall_time < idle_time)) + continue; + + /* load is the percentage of time not spent in idle */ + load = 100 * (wall_time - idle_time) / wall_time; + + /* keep track of combined load across all CPUs */ + total_load += load; + + /* keep track of highest single load across all CPUs */ + if (load > max_load) + max_load = load; + } + + /* calculate the average load across all related CPUs */ + avg_load = total_load / num_online_cpus(); + + + /* + * hotplug load accounting + * average load over multiple sampling periods + */ + + /* how many sampling periods do we use for hotplug decisions? */ + periods = max(dbs_tuners_ins.hotplug_in_sampling_periods, + dbs_tuners_ins.hotplug_out_sampling_periods); + + /* store avg_load in the circular buffer */ + dbs_tuners_ins.hotplug_load_history[dbs_tuners_ins.hotplug_load_index] + = avg_load; + + /* compute average load across in & out sampling periods */ + for (i = 0, j = dbs_tuners_ins.hotplug_load_index; + i < periods; i++, j--) { + if (i < dbs_tuners_ins.hotplug_in_sampling_periods) + hotplug_in_avg_load += + dbs_tuners_ins.hotplug_load_history[j]; + if (i < dbs_tuners_ins.hotplug_out_sampling_periods) + hotplug_out_avg_load += + dbs_tuners_ins.hotplug_load_history[j]; + + if (j == 0) + j = periods; + } + + hotplug_in_avg_load = hotplug_in_avg_load / + dbs_tuners_ins.hotplug_in_sampling_periods; + + hotplug_out_avg_load = hotplug_out_avg_load / + dbs_tuners_ins.hotplug_out_sampling_periods; + + /* return to first element if we're at the circular buffer's end */ + if (++dbs_tuners_ins.hotplug_load_index == periods) + dbs_tuners_ins.hotplug_load_index = 0; + + /* check for frequency increase */ + if (avg_load > dbs_tuners_ins.up_threshold) { + /* should we enable auxillary CPUs? */ + if (num_online_cpus() < 2 && hotplug_in_avg_load > + dbs_tuners_ins.up_threshold) { + /* hotplug with cpufreq is nasty + * a call to cpufreq_governor_dbs may cause a lockup. + * wq is not running here so its safe. + */ + mutex_unlock(&this_dbs_info->timer_mutex); + cpu_up(1); + mutex_lock(&this_dbs_info->timer_mutex); + goto out; + } + + /* increase to highest frequency supported */ + if (policy->cur < policy->max) + __cpufreq_driver_target(policy, policy->max, + CPUFREQ_RELATION_H); + + goto out; + } + + /* check for frequency decrease */ + if (avg_load < dbs_tuners_ins.down_threshold) { + /* are we at the minimum frequency already? */ + if (policy->cur == policy->min) { + /* should we disable auxillary CPUs? */ + if (num_online_cpus() > 1 && hotplug_out_avg_load < + dbs_tuners_ins.down_threshold) { + mutex_unlock(&this_dbs_info->timer_mutex); + cpu_down(1); + mutex_lock(&this_dbs_info->timer_mutex); + } + goto out; + } + + /* bump down to the next lowest frequency in the table */ + if (cpufreq_frequency_table_next_lowest(policy, + this_dbs_info->freq_table, &index)) { + pr_err("%s: failed to get next lowest frequency\n", + __func__); + goto out; + } + + __cpufreq_driver_target(policy, + this_dbs_info->freq_table[index].frequency, + CPUFREQ_RELATION_L); + } +out: + return; +} + +static void do_dbs_timer(struct work_struct *work) +{ + struct cpu_dbs_info_s *dbs_info = + container_of(work, struct cpu_dbs_info_s, work.work); + unsigned int cpu = dbs_info->cpu; + + /* We want all related CPUs to do sampling nearly on same jiffy */ + int delay = usecs_to_jiffies(dbs_tuners_ins.sampling_rate); + + mutex_lock(&dbs_info->timer_mutex); + dbs_check_cpu(dbs_info); + queue_delayed_work_on(cpu, khotplug_wq, &dbs_info->work, delay); + mutex_unlock(&dbs_info->timer_mutex); +} + +static inline void dbs_timer_init(struct cpu_dbs_info_s *dbs_info) +{ + /* We want all related CPUs to do sampling nearly on same jiffy */ + int delay = usecs_to_jiffies(dbs_tuners_ins.sampling_rate); + delay -= jiffies % delay; + + INIT_DELAYED_WORK_DEFERRABLE(&dbs_info->work, do_dbs_timer); + queue_delayed_work_on(dbs_info->cpu, khotplug_wq, &dbs_info->work, + delay); +} + +static inline void dbs_timer_exit(struct cpu_dbs_info_s *dbs_info) +{ + cancel_delayed_work_sync(&dbs_info->work); +} + +static int cpufreq_governor_dbs(struct cpufreq_policy *policy, + unsigned int event) +{ + unsigned int cpu = policy->cpu; + struct cpu_dbs_info_s *this_dbs_info; + unsigned int i, j, max_periods; + int rc; + + this_dbs_info = &per_cpu(hp_cpu_dbs_info, cpu); + + switch (event) { + case CPUFREQ_GOV_START: + if ((!cpu_online(cpu)) || (!policy->cur)) + return -EINVAL; + + mutex_lock(&dbs_mutex); + dbs_enable++; + for_each_cpu(j, policy->cpus) { + struct cpu_dbs_info_s *j_dbs_info; + j_dbs_info = &per_cpu(hp_cpu_dbs_info, j); + j_dbs_info->cur_policy = policy; + + j_dbs_info->prev_cpu_idle = get_cpu_idle_time(j, + &j_dbs_info->prev_cpu_wall); + if (dbs_tuners_ins.ignore_nice) { + j_dbs_info->prev_cpu_nice = + kstat_cpu(j).cpustat.nice; + } + + max_periods = max(DEFAULT_HOTPLUG_IN_SAMPLING_PERIODS, + DEFAULT_HOTPLUG_OUT_SAMPLING_PERIODS); + dbs_tuners_ins.hotplug_load_history = kmalloc( + (sizeof(unsigned int) * max_periods), + GFP_KERNEL); + if (!dbs_tuners_ins.hotplug_load_history) { + WARN_ON(1); + return -ENOMEM; + } + for (i = 0; i < max_periods; i++) + dbs_tuners_ins.hotplug_load_history[i] = 50; + } + this_dbs_info->cpu = cpu; + this_dbs_info->freq_table = cpufreq_frequency_get_table(cpu); + /* + * Start the timerschedule work, when this governor + * is used for first time + */ + if (dbs_enable == 1) { + rc = sysfs_create_group(cpufreq_global_kobject, + &dbs_attr_group); + if (rc) { + mutex_unlock(&dbs_mutex); + return rc; + } + } + mutex_unlock(&dbs_mutex); + + mutex_init(&this_dbs_info->timer_mutex); + dbs_timer_init(this_dbs_info); + break; + + case CPUFREQ_GOV_STOP: + dbs_timer_exit(this_dbs_info); + + mutex_lock(&dbs_mutex); + mutex_destroy(&this_dbs_info->timer_mutex); + dbs_enable--; + mutex_unlock(&dbs_mutex); + if (!dbs_enable) + sysfs_remove_group(cpufreq_global_kobject, + &dbs_attr_group); + kfree(dbs_tuners_ins.hotplug_load_history); + /* + * XXX BIG CAVEAT: Stopping the governor with CPU1 offline + * will result in it remaining offline until the user onlines + * it again. It is up to the user to do this (for now). + */ + break; + + case CPUFREQ_GOV_LIMITS: + mutex_lock(&this_dbs_info->timer_mutex); + if (policy->max < this_dbs_info->cur_policy->cur) + __cpufreq_driver_target(this_dbs_info->cur_policy, + policy->max, CPUFREQ_RELATION_H); + else if (policy->min > this_dbs_info->cur_policy->cur) + __cpufreq_driver_target(this_dbs_info->cur_policy, + policy->min, CPUFREQ_RELATION_L); + mutex_unlock(&this_dbs_info->timer_mutex); + break; + } + return 0; +} + +static int __init cpufreq_gov_dbs_init(void) +{ + int err; + cputime64_t wall; + u64 idle_time; + int cpu = get_cpu(); + + idle_time = get_cpu_idle_time_us(cpu, &wall); + put_cpu(); + if (idle_time != -1ULL) { + dbs_tuners_ins.up_threshold = DEFAULT_UP_FREQ_MIN_LOAD; + } else { + pr_err("cpufreq-hotplug: %s: assumes CONFIG_NO_HZ\n", + __func__); + return -EINVAL; + } + + khotplug_wq = create_workqueue("khotplug"); + if (!khotplug_wq) { + pr_err("Creation of khotplug failed\n"); + return -EFAULT; + } + err = cpufreq_register_governor(&cpufreq_gov_hotplug); + if (err) + destroy_workqueue(khotplug_wq); + + return err; +} + +static void __exit cpufreq_gov_dbs_exit(void) +{ + cpufreq_unregister_governor(&cpufreq_gov_hotplug); + destroy_workqueue(khotplug_wq); +} + +MODULE_AUTHOR("Mike Turquette <mturquette@ti.com>"); +MODULE_DESCRIPTION("'cpufreq_hotplug' - cpufreq governor for dynamic frequency scaling and CPU hotplugging"); +MODULE_LICENSE("GPL"); + +#ifdef CONFIG_CPU_FREQ_DEFAULT_GOV_HOTPLUG +fs_initcall(cpufreq_gov_dbs_init); +#else +module_init(cpufreq_gov_dbs_init); +#endif +module_exit(cpufreq_gov_dbs_exit); diff --git a/drivers/cpufreq/freq_table.c b/drivers/cpufreq/freq_table.c index 05432216e224..11a307b81fb3 100644 --- a/drivers/cpufreq/freq_table.c +++ b/drivers/cpufreq/freq_table.c @@ -13,6 +13,7 @@ #include <linux/module.h> #include <linux/init.h> #include <linux/cpufreq.h> +#include <linux/err.h> #define dprintk(msg...) \ cpufreq_debug_printk(CPUFREQ_DEBUG_CORE, "freq-table", msg) @@ -174,6 +175,78 @@ int cpufreq_frequency_table_target(struct cpufreq_policy *policy, } EXPORT_SYMBOL_GPL(cpufreq_frequency_table_target); +int cpufreq_frequency_table_next_lowest(struct cpufreq_policy *policy, + struct cpufreq_frequency_table *table, int *index) +{ + unsigned int cur_freq; + unsigned int next_lowest_freq; + int optimal_index = -1; + int i = 0; + + if (!policy || IS_ERR(policy) || !table || IS_ERR(table) || + !index || IS_ERR(index)) + return -ENOMEM; + + cur_freq = policy->cur; + next_lowest_freq = policy->min; + + /* we're at the lowest frequency in the table already, bail out */ + if (cur_freq == policy->min) + return -EINVAL; + + /* walk the list, find closest freq to cur_freq that is below it */ + while(table[i].frequency != CPUFREQ_TABLE_END) { + if (table[i].frequency < cur_freq && + table[i].frequency >= next_lowest_freq) { + next_lowest_freq = table[i].frequency; + optimal_index = table[i].index; + } + + i++; + } + + *index = optimal_index; + + return 0; +} +EXPORT_SYMBOL_GPL(cpufreq_frequency_table_next_lowest); + +int cpufreq_frequency_table_next_highest(struct cpufreq_policy *policy, + struct cpufreq_frequency_table *table, int *index) +{ + unsigned int cur_freq; + unsigned int next_higher_freq; + int optimal_index = -1; + int i = 0; + + if (!policy || IS_ERR(policy) || !table || IS_ERR(table) || + !index || IS_ERR(index)) + return -ENOMEM; + + cur_freq = policy->cur; + next_higher_freq = policy->max; + + /* we're at the highest frequency in the table already, bail out */ + if (cur_freq == policy->max) + return -EINVAL; + + /* walk the list, find closest freq to cur_freq that is above it */ + while(table[i].frequency != CPUFREQ_TABLE_END) { + if (table[i].frequency > cur_freq && + table[i].frequency <= next_higher_freq) { + next_higher_freq = table[i].frequency; + optimal_index = table[i].index; + } + + i++; + } + + *index = optimal_index; + + return 0; +} +EXPORT_SYMBOL_GPL(cpufreq_frequency_table_next_highest); + static DEFINE_PER_CPU(struct cpufreq_frequency_table *, cpufreq_show_table); /** * show_available_freqs - show available frequencies for the specified CPU diff --git a/drivers/i2c/busses/i2c-omap.c b/drivers/i2c/busses/i2c-omap.c index 58a58c7eaa17..42a511abab9a 100644 --- a/drivers/i2c/busses/i2c-omap.c +++ b/drivers/i2c/busses/i2c-omap.c @@ -1140,7 +1140,7 @@ omap_i2c_remove(struct platform_device *pdev) } #ifdef CONFIG_SUSPEND -static int omap_i2c_suspend(struct device *dev) +static int omap_i2c_suspend_noirq(struct device *dev) { if (!pm_runtime_suspended(dev)) if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_suspend) @@ -1149,7 +1149,7 @@ static int omap_i2c_suspend(struct device *dev) return 0; } -static int omap_i2c_resume(struct device *dev) +static int omap_i2c_resume_noirq(struct device *dev) { if (!pm_runtime_suspended(dev)) if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume) @@ -1159,8 +1159,8 @@ static int omap_i2c_resume(struct device *dev) } static struct dev_pm_ops omap_i2c_pm_ops = { - .suspend = omap_i2c_suspend, - .resume = omap_i2c_resume, + .suspend_noirq = omap_i2c_suspend_noirq, + .resume_noirq = omap_i2c_resume_noirq, }; #define OMAP_I2C_PM_OPS (&omap_i2c_pm_ops) #else diff --git a/drivers/tty/serial/omap-serial.c b/drivers/tty/serial/omap-serial.c index 47cadf474149..0275c284707e 100644 --- a/drivers/tty/serial/omap-serial.c +++ b/drivers/tty/serial/omap-serial.c @@ -1335,6 +1335,55 @@ static struct platform_driver serial_omap_driver = { }, }; +int omap_uart_active(int num, u32 timeout) +{ + struct uart_omap_port *up = ui[num]; + struct circ_buf *xmit; + unsigned int status; + + if(num >= OMAP_MAX_HSUART_PORTS) + return 0; + + /* Though when UART's initialised this can never happen, + * but during initialisation, it can happen the "ui" + * structure is not initialized and the timer kicks + * in. This would result in a NULL value, resulting + * in crash. + */ + up = ui[num]; + if (up == NULL) + return 0; + + if (!up->port_activity) + return 1; + + /* Check for recent driver activity. If time delta from now + * to last activty < "uart idle timeout" second keep clocks on. + */ + if (((jiffies - up->port_activity) < timeout)) + return 1; + + xmit = &up->port.state->xmit; + if (!(uart_circ_empty(xmit) || uart_tx_stopped(&up->port))) + return 1; + + status = serial_in(up, UART_LSR); + /* TX hardware not empty */ + if (!(status & (UART_LSR_TEMT | UART_LSR_THRE))) + return 1; + + /* Any rx activity? */ + if (status & UART_LSR_DR) + return 1; + + /* Check if DMA channels are active */ + if (up->use_dma && (up->uart_dma.rx_dma_channel != OMAP_UART_DMA_CH_FREE || + up->uart_dma.tx_dma_channel != OMAP_UART_DMA_CH_FREE)) + return 1; + return 0; +} +EXPORT_SYMBOL(omap_uart_active); + static int __init serial_omap_init(void) { int ret; diff --git a/include/linux/clockchips.h b/include/linux/clockchips.h index fc53492b6ad7..164b5c4d78a6 100644 --- a/include/linux/clockchips.h +++ b/include/linux/clockchips.h @@ -132,6 +132,8 @@ extern int clockevents_program_event(struct clock_event_device *dev, extern void clockevents_handle_noop(struct clock_event_device *dev); +extern int clockevents_reconfigure(struct clock_event_device *ce, u32 freq, u32 minsec); + static inline void clockevents_calc_mult_shift(struct clock_event_device *ce, u32 freq, u32 minsec) { diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h index 9343dd3de858..6cbc3df44796 100644 --- a/include/linux/cpufreq.h +++ b/include/linux/cpufreq.h @@ -355,6 +355,9 @@ extern struct cpufreq_governor cpufreq_gov_ondemand; #elif defined(CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE) extern struct cpufreq_governor cpufreq_gov_conservative; #define CPUFREQ_DEFAULT_GOVERNOR (&cpufreq_gov_conservative) +#elif defined(CONFIG_CPU_FREQ_DEFAULT_GOV_HOTPLUG) +extern struct cpufreq_governor cpufreq_gov_hotplug; +#define CPUFREQ_DEFAULT_GOVERNOR (&cpufreq_gov_hotplug) #endif @@ -396,6 +399,15 @@ void cpufreq_frequency_table_get_attr(struct cpufreq_frequency_table *table, void cpufreq_frequency_table_put_attr(unsigned int cpu); +/* the following are for use in governors, or anywhere else */ +extern int cpufreq_frequency_table_next_lowest(struct cpufreq_policy *policy, + struct cpufreq_frequency_table *table, + int *index); + +extern int cpufreq_frequency_table_next_highest(struct cpufreq_policy *policy, + struct cpufreq_frequency_table *table, + int *index); + /********************************************************************* * UNIFIED DEBUG HELPERS * diff --git a/include/linux/opp.h b/include/linux/opp.h index 5449945d589f..7020e9736fc5 100644 --- a/include/linux/opp.h +++ b/include/linux/opp.h @@ -94,12 +94,20 @@ static inline int opp_disable(struct device *dev, unsigned long freq) #if defined(CONFIG_CPU_FREQ) && defined(CONFIG_PM_OPP) int opp_init_cpufreq_table(struct device *dev, struct cpufreq_frequency_table **table); +void opp_free_cpufreq_table(struct device *dev, + struct cpufreq_frequency_table **table); #else static inline int opp_init_cpufreq_table(struct device *dev, struct cpufreq_frequency_table **table) { return -EINVAL; } + +static inline +void opp_free_cpufreq_table(struct device *dev, + struct cpufreq_frequency_table **table) +{ +} #endif /* CONFIG_CPU_FREQ */ #endif /* __LINUX_OPP_H__ */ diff --git a/kernel/time/clockevents.c b/kernel/time/clockevents.c index 0d74b9ba90c8..0832b22a8b31 100644 --- a/kernel/time/clockevents.c +++ b/kernel/time/clockevents.c @@ -133,6 +133,30 @@ int clockevents_program_event(struct clock_event_device *dev, ktime_t expires, } /** + * clockevents_reconfigure - Reconfigure and reprogram a clock event device. + * @dev: device to modify + * @freq: new device frequency + * @secr: guaranteed runtime conversion range in seconds + * + * Reconfigure and reprogram a clock event device in oneshot + * mode. Must only be called from low level idle code where + * interaction with hrtimers/nohz code etc. is not possible and + * guaranteed not to conflict. Must be called with interrupts + * disabled! + * Returns 0 on success, -ETIME when the event is in the past or + * -EINVAL when called with invalid parameters. + */ +int clockevents_reconfigure(struct clock_event_device *dev, u32 freq, u32 secr) +{ + if (dev->mode != CLOCK_EVT_MODE_ONESHOT) + return -EINVAL; + + clockevents_calc_mult_shift(dev, freq, secr ? secr : 1); + + return clockevents_program_event(dev, dev->next_event, ktime_get()); +} + +/** * clockevents_register_notifier - register a clock events change listener */ int clockevents_register_notifier(struct notifier_block *nb) |