diff options
Diffstat (limited to 'arch/blackfin/mach-common')
-rw-r--r-- | arch/blackfin/mach-common/Makefile | 5 | ||||
-rw-r--r-- | arch/blackfin/mach-common/cpufreq.c | 26 | ||||
-rw-r--r-- | arch/blackfin/mach-common/dpmc.c | 137 | ||||
-rw-r--r-- | arch/blackfin/mach-common/dpmc_modes.S (renamed from arch/blackfin/mach-common/dpmc.S) | 27 | ||||
-rw-r--r-- | arch/blackfin/mach-common/entry.S | 113 |
5 files changed, 240 insertions, 68 deletions
diff --git a/arch/blackfin/mach-common/Makefile b/arch/blackfin/mach-common/Makefile index 393081e9b680..422bfee34adc 100644 --- a/arch/blackfin/mach-common/Makefile +++ b/arch/blackfin/mach-common/Makefile @@ -6,5 +6,6 @@ obj-y := \ cache.o cacheinit.o entry.o \ interrupt.o lock.o irqpanic.o arch_checks.o ints-priority.o -obj-$(CONFIG_PM) += pm.o dpmc.o -obj-$(CONFIG_CPU_FREQ) += cpufreq.o +obj-$(CONFIG_PM) += pm.o dpmc_modes.o +obj-$(CONFIG_CPU_FREQ) += cpufreq.o +obj-$(CONFIG_CPU_VOLTAGE) += dpmc.o diff --git a/arch/blackfin/mach-common/cpufreq.c b/arch/blackfin/mach-common/cpufreq.c index ed81e00d20e1..75cdad291e88 100644 --- a/arch/blackfin/mach-common/cpufreq.c +++ b/arch/blackfin/mach-common/cpufreq.c @@ -62,6 +62,14 @@ static struct bfin_dpm_state { unsigned int tscale; /* change the divider on the core timer interrupt */ } dpm_state_table[3]; +/* + normalized to maximum frequncy offset for CYCLES, + used in time-ts cycles clock source, but could be used + somewhere also. + */ +unsigned long long __bfin_cycles_off; +unsigned int __bfin_cycles_mod; + /**************************************************************************/ static unsigned int bfin_getfreq(unsigned int cpu) @@ -80,6 +88,7 @@ static int bfin_target(struct cpufreq_policy *policy, unsigned int index, plldiv, tscale; unsigned long flags, cclk_hz; struct cpufreq_freqs freqs; + cycles_t cycles; if (cpufreq_frequency_table_target(policy, bfin_freq_table, target_freq, relation, &index)) @@ -101,8 +110,14 @@ static int bfin_target(struct cpufreq_policy *policy, bfin_write_PLL_DIV(plldiv); /* we have to adjust the core timer, because it is using cclk */ bfin_write_TSCALE(tscale); + cycles = get_cycles(); SSYNC(); + cycles += 10; /* ~10 cycles we loose after get_cycles() */ + __bfin_cycles_off += (cycles << __bfin_cycles_mod) - (cycles << index); + __bfin_cycles_mod = index; local_irq_restore(flags); + /* TODO: just test case for cycles clock source, remove later */ + pr_debug("cpufreq: done\n"); cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); return 0; @@ -119,22 +134,13 @@ static int __init __bfin_cpu_init(struct cpufreq_policy *policy) unsigned long cclk, sclk, csel, min_cclk; int index; -#ifdef CONFIG_CYCLES_CLOCKSOURCE -/* - * Clocksource CYCLES is still CONTINUOUS but not longer MONOTONIC in case we enable - * CPU frequency scaling, since CYCLES runs off Core Clock. - */ - printk(KERN_WARNING "CPU frequency scaling not supported: Clocksource not suitable\n" - return -ENODEV; -#endif - if (policy->cpu != 0) return -EINVAL; cclk = get_cclk(); sclk = get_sclk(); -#if ANOMALY_05000273 +#if ANOMALY_05000273 || (!defined(CONFIG_BF54x) && defined(CONFIG_BFIN_DCACHE)) min_cclk = sclk * 2; #else min_cclk = sclk; diff --git a/arch/blackfin/mach-common/dpmc.c b/arch/blackfin/mach-common/dpmc.c new file mode 100644 index 000000000000..02c7efd1bcf4 --- /dev/null +++ b/arch/blackfin/mach-common/dpmc.c @@ -0,0 +1,137 @@ +/* + * Copyright 2008 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/types.h> +#include <linux/cpufreq.h> + +#include <asm/delay.h> +#include <asm/dpmc.h> + +#define DRIVER_NAME "bfin dpmc" + +#define dprintk(msg...) \ + cpufreq_debug_printk(CPUFREQ_DEBUG_DRIVER, DRIVER_NAME, msg) + +struct bfin_dpmc_platform_data *pdata; + +/** + * bfin_set_vlev - Update VLEV field in VR_CTL Reg. + * Avoid BYPASS sequence + */ +static void bfin_set_vlev(unsigned int vlev) +{ + unsigned pll_lcnt; + + pll_lcnt = bfin_read_PLL_LOCKCNT(); + + bfin_write_PLL_LOCKCNT(1); + bfin_write_VR_CTL((bfin_read_VR_CTL() & ~VLEV) | vlev); + bfin_write_PLL_LOCKCNT(pll_lcnt); +} + +/** + * bfin_get_vlev - Get CPU specific VLEV from platform device data + */ +static unsigned int bfin_get_vlev(unsigned int freq) +{ + int i; + + if (!pdata) + goto err_out; + + freq >>= 16; + + for (i = 0; i < pdata->tabsize; i++) + if (freq <= (pdata->tuple_tab[i] & 0xFFFF)) + return pdata->tuple_tab[i] >> 16; + +err_out: + printk(KERN_WARNING "DPMC: No suitable CCLK VDDINT voltage pair found\n"); + return VLEV_120; +} + +#ifdef CONFIG_CPU_FREQ +static int +vreg_cpufreq_notifier(struct notifier_block *nb, unsigned long val, void *data) +{ + struct cpufreq_freqs *freq = data; + + if (val == CPUFREQ_PRECHANGE && freq->old < freq->new) { + bfin_set_vlev(bfin_get_vlev(freq->new)); + udelay(pdata->vr_settling_time); /* Wait until Volatge settled */ + + } else if (val == CPUFREQ_POSTCHANGE && freq->old > freq->new) + bfin_set_vlev(bfin_get_vlev(freq->new)); + + return 0; +} + +static struct notifier_block vreg_cpufreq_notifier_block = { + .notifier_call = vreg_cpufreq_notifier +}; +#endif /* CONFIG_CPU_FREQ */ + +/** + * bfin_dpmc_probe - + * + */ +static int __devinit bfin_dpmc_probe(struct platform_device *pdev) +{ + if (pdev->dev.platform_data) + pdata = pdev->dev.platform_data; + else + return -EINVAL; + + return cpufreq_register_notifier(&vreg_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); +} + +/** + * bfin_dpmc_remove - + */ +static int __devexit bfin_dpmc_remove(struct platform_device *pdev) +{ + pdata = NULL; + return cpufreq_unregister_notifier(&vreg_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); +} + +struct platform_driver bfin_dpmc_device_driver = { + .probe = bfin_dpmc_probe, + .remove = __devexit_p(bfin_dpmc_remove), + .driver = { + .name = DRIVER_NAME, + } +}; + +/** + * bfin_dpmc_init - Init driver + */ +static int __init bfin_dpmc_init(void) +{ + return platform_driver_register(&bfin_dpmc_device_driver); +} +module_init(bfin_dpmc_init); + +/** + * bfin_dpmc_exit - break down driver + */ +static void __exit bfin_dpmc_exit(void) +{ + platform_driver_unregister(&bfin_dpmc_device_driver); +} +module_exit(bfin_dpmc_exit); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("cpu power management driver for Blackfin"); +MODULE_LICENSE("GPL"); diff --git a/arch/blackfin/mach-common/dpmc.S b/arch/blackfin/mach-common/dpmc_modes.S index 9d45aa3265b1..b7981d31c392 100644 --- a/arch/blackfin/mach-common/dpmc.S +++ b/arch/blackfin/mach-common/dpmc_modes.S @@ -1,30 +1,7 @@ /* - * File: arch/blackfin/mach-common/dpmc.S - * Based on: - * Author: LG Soft India + * Copyright 2004-2008 Analog Devices Inc. * - * Created: ? - * Description: Watchdog Timer APIs - * - * Modified: - * Copyright 2004-2006 Analog Devices Inc. - * - * Bugs: Enter bugs at http://blackfin.uclinux.org/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see the file COPYING, or write - * to the Free Software Foundation, Inc., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * Licensed under the GPL-2 or later. */ #include <linux/linkage.h> diff --git a/arch/blackfin/mach-common/entry.S b/arch/blackfin/mach-common/entry.S index f2fb87e9a46e..038f70e0be65 100644 --- a/arch/blackfin/mach-common/entry.S +++ b/arch/blackfin/mach-common/entry.S @@ -151,26 +151,62 @@ ENTRY(_ex_soft_bp) ENDPROC(_ex_soft_bp) ENTRY(_ex_single_step) + /* If we just returned from an interrupt, the single step event is + for the RTI instruction. */ r7 = retx; r6 = reti; cc = r7 == r6; - if cc jump _bfin_return_from_exception - r7 = syscfg; - bitclr (r7, 0); - syscfg = R7; + if cc jump _bfin_return_from_exception; + /* If we were in user mode, do the single step normally. */ p5.l = lo(IPEND); p5.h = hi(IPEND); r6 = [p5]; - cc = bittst(r6, 5); - if !cc jump _ex_trap_c; - p4.l = lo(EVT5); - p4.h = hi(EVT5); - r6.h = _exception_to_level5; - r6.l = _exception_to_level5; - r7 = [p4]; - cc = r6 == r7; - if !cc jump _ex_trap_c; + r7 = 0xffe0 (z); + r7 = r7 & r6; + cc = r7 == 0; + if !cc jump 1f; + + /* Single stepping only a single instruction, so clear the trace + * bit here. */ + r7 = syscfg; + bitclr (r7, 0); + syscfg = R7; + jump _ex_trap_c; + +1: + /* + * We were in an interrupt handler. By convention, all of them save + * SYSCFG with their first instruction, so by checking whether our + * RETX points at the entry point, we can determine whether to allow + * a single step, or whether to clear SYSCFG. + * + * First, find out the interrupt level and the event vector for it. + */ + p5.l = lo(EVT0); + p5.h = hi(EVT0); + p5 += -4; +2: + r7 = rot r7 by -1; + p5 += 4; + if !cc jump 2b; + + /* What we actually do is test for the _second_ instruction in the + * IRQ handler. That way, if there are insns following the restore + * of SYSCFG after leaving the handler, we will not turn off SYSCFG + * for them. */ + + r7 = [p5]; + r7 += 2; + r6 = RETX; + cc = R7 == R6; + if !cc jump _bfin_return_from_exception; + + r7 = syscfg; + bitclr (r7, 0); + syscfg = R7; + + /* Fall through to _bfin_return_from_exception. */ ENDPROC(_ex_single_step) ENTRY(_bfin_return_from_exception) @@ -234,20 +270,26 @@ ENTRY(_ex_trap_c) p5.l = _saved_icplb_fault_addr; [p5] = r7; - p4.l = __retx; - p4.h = __retx; + p4.l = _excpt_saved_stuff; + p4.h = _excpt_saved_stuff; + r6 = retx; [p4] = r6; - p4.l = lo(SAFE_USER_INSTRUCTION); - p4.h = hi(SAFE_USER_INSTRUCTION); - retx = p4; + + r6 = SYSCFG; + [p4 + 4] = r6; + BITCLR(r6, 0); + SYSCFG = r6; /* Disable all interrupts, but make sure level 5 is enabled so * we can switch to that level. Save the old mask. */ cli r6; - p4.l = _excpt_saved_imask; - p4.h = _excpt_saved_imask; - [p4] = r6; + [p4 + 8] = r6; + + p4.l = lo(SAFE_USER_INSTRUCTION); + p4.h = hi(SAFE_USER_INSTRUCTION); + retx = p4; + r6 = 0x3f; sti r6; @@ -295,6 +337,11 @@ ENTRY(_double_fault) */ SAVE_ALL_SYS + /* The dumping functions expect the return address in the RETI + * slot. */ + r6 = retx; + [sp + PT_PC] = r6; + r0 = sp; /* stack frame pt_regs pointer argument ==> r0 */ SP += -12; call _double_fault_c; @@ -307,16 +354,17 @@ ENDPROC(_double_fault) ENTRY(_exception_to_level5) SAVE_ALL_SYS - p4.l = __retx; - p4.h = __retx; + p4.l = _excpt_saved_stuff; + p4.h = _excpt_saved_stuff; r6 = [p4]; [sp + PT_PC] = r6; + r6 = [p4 + 4]; + [sp + PT_SYSCFG] = r6; + /* Restore interrupt mask. We haven't pushed RETI, so this * doesn't enable interrupts until we return from this handler. */ - p4.l = _excpt_saved_imask; - p4.h = _excpt_saved_imask; - r6 = [p4]; + r6 = [p4 + 8]; sti r6; /* Restore the hardware error vector. */ @@ -1344,7 +1392,14 @@ ENTRY(_sys_call_table) .rept NR_syscalls-(.-_sys_call_table)/4 .long _sys_ni_syscall .endr -_excpt_saved_imask: + + /* + * Used to save the real RETX, IMASK and SYSCFG when temporarily + * storing safe values across the transition from exception to IRQ5. + */ +_excpt_saved_stuff: + .long 0; + .long 0; .long 0; _exception_stack: @@ -1358,7 +1413,3 @@ _exception_stack_top: _last_cplb_fault_retx: .long 0; #endif - /* Used to save the real RETX when temporarily storing a safe - * return address. */ -__retx: - .long 0; |