forked from rubenslte/android_kernel_samsung_msm8226
sched_clock: Use seqcount instead of rolling our own We're going to increase the cyc value to 64 bits in the near future. Doing that is going to break the custom seqcount implementation in the sched_clock code because 64 bit numbers aren't guaranteed to be atomic. Replace the cyc_copy with a seqcount to avoid this problem. Cc: Russell King <linux@arm.linux.org.uk> Acked-by: Will Deacon <will.deacon@arm.com> Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> Signed-off-by: John Stultz <john.stultz@linaro.org> Git-commit: 85c3d2dd15be4d577a37ffb8bbbd019fc8e3280a Git-repo: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git [imaund@codeaurora.org: resolve merge conflicts] Signed-off-by: Ian Maund <imaund@codeaurora.org> Change-Id: Ic30515ef65e5d3ec008f03ae020a9599e7c01fb2 sched_clock: Use an hrtimer instead of timer In the next patch we're going to increase the number of bits that the generic sched_clock can handle to be greater than 32. With more than 32 bits the wraparound time can be larger than what can fit into the units that msecs_to_jiffies takes (unsigned int). Luckily, the wraparound is initially calculated in nanoseconds which we can easily use with hrtimers, so switch to using an hrtimer. Change-Id: Id6059fae75863ddd3972f4dcac1cf7b803b09ac9 Cc: Russell King <linux@arm.linux.org.uk> Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> [jstultz: Fixup hrtimer intitialization order issue] Signed-off-by: John Stultz <john.stultz@linaro.org> Git-commit: a08ca5d1089da03724f96fa0870c64968e66765b Git-repo: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git Signed-off-by: Ian Maund <imaund@codeaurora.org> sched_clock: Add support for >32 bit sched_clock The ARM architected system counter has at least 56 usable bits. Add support for counters with more than 32 bits to the generic sched_clock implementation so we can increase the time between wakeups due to dealing with wrap-around on these devices while benefiting from the irqtime accounting and suspend/resume handling that the generic sched_clock code already has. On my system using 56 bits over 32 bits changes the wraparound time from a few minutes to an hour. For faster running counters (GHz range) this is even more important because we may not be able to execute the timer in time to deal with the wraparound if only 32 bits are used. We choose a maxsec value of 3600 seconds because we assume no system will go idle for more than an hour. In the future we may need to increase this value. Note: All users should switch over to the 64-bit read function so we can remove setup_sched_clock() in favor of sched_clock_register(). Change-Id: I81a4b7102db5316bd31d1fa54e2d801f1ee133c1 Cc: Russell King <linux@arm.linux.org.uk> Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> Signed-off-by: John Stultz <john.stultz@linaro.org> Git-commit: e7e3ff1bfe9c42ee31172e9afdc0383a9e595e29 Git-repo: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git Signed-off-by: Ian Maund <imaund@codeaurora.org> sched_clock: Avoid corrupting hrtimer tree during suspend During suspend we call sched_clock_poll() to update the epoch and accumulated time and reprogram the sched_clock_timer to fire before the next wrap-around time. Unfortunately, sched_clock_poll() doesn't restart the timer, instead it relies on the hrtimer layer to do that and during suspend we aren't calling that function from the hrtimer layer. Instead, we're reprogramming the expires time while the hrtimer is enqueued, which can cause the hrtimer tree to be corrupted. Furthermore, we restart the timer during suspend but we update the epoch during resume which seems counter-intuitive. Let's fix this by saving the accumulated state and canceling the timer during suspend. On resume we can update the epoch and restart the timer similar to what we would do if we were starting the clock for the first time. Change-Id: Iee2a1cca42e5b681347ea0607e9af420a63892d7 CRs-Fixed: 696826 Fixes: a08ca5d1089d "sched_clock: Use an hrtimer instead of timer" Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> sched_clock: Make ARM's sched_clock generic for all architectures Nothing about the sched_clock implementation in the ARM port is specific to the architecture. Generalize the code so that other architectures can use it by selecting GENERIC_SCHED_CLOCK. Change-Id: I1e846fd1fb87ec1217ea84f1e2e02596c9eaa96b Signed-off-by: Stephen Boyd <sboyd@codeaurora.org> [jstultz: Merge minor collisions with other patches in my tree] Signed-off-by: John Stultz <john.stultz@linaro.org> Change-Id: Id4ff982d3b5dfdc2fa172f3bdbddc6021134c932 Signed-off-by: Kevin F. Haggerty <haggertk@lineageos.org>
648 lines
15 KiB
C
648 lines
15 KiB
C
/*
|
|
* linux/arch/arm/kernel/arch_timer.c
|
|
*
|
|
* Copyright (C) 2011 ARM Ltd.
|
|
* All Rights Reserved
|
|
*
|
|
* 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/delay.h>
|
|
#include <linux/timex.h>
|
|
#include <linux/device.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/cpu_pm.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/clockchips.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/io.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/export.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sched_clock.h>
|
|
|
|
#include <asm/cputype.h>
|
|
#include <asm/delay.h>
|
|
#include <asm/localtimer.h>
|
|
#include <asm/arch_timer.h>
|
|
#include <asm/hardware/gic.h>
|
|
#include <asm/system_info.h>
|
|
|
|
static unsigned long arch_timer_rate;
|
|
static int arch_timer_spi;
|
|
static int arch_timer_ppi;
|
|
static int arch_timer_ppi2;
|
|
|
|
static struct clock_event_device __percpu **arch_timer_evt;
|
|
static void __iomem *timer_base;
|
|
|
|
static struct delay_timer arch_delay_timer;
|
|
|
|
/*
|
|
* Architected system timer support.
|
|
*/
|
|
|
|
#define ARCH_TIMER_CTRL_ENABLE (1 << 0)
|
|
#define ARCH_TIMER_CTRL_IT_MASK (1 << 1)
|
|
#define ARCH_TIMER_CTRL_IT_STAT (1 << 2)
|
|
|
|
#define ARCH_TIMER_REG_CTRL 0
|
|
#define ARCH_TIMER_REG_FREQ 1
|
|
#define ARCH_TIMER_REG_TVAL 2
|
|
|
|
/* Iomapped Register Offsets */
|
|
#define QTIMER_CNTP_LOW_REG 0x000
|
|
#define QTIMER_CNTP_HIGH_REG 0x004
|
|
#define QTIMER_CNTV_LOW_REG 0x008
|
|
#define QTIMER_CNTV_HIGH_REG 0x00C
|
|
#define QTIMER_CTRL_REG 0x02C
|
|
#define QTIMER_FREQ_REG 0x010
|
|
#define QTIMER_CNTP_TVAL_REG 0x028
|
|
#define QTIMER_CNTV_TVAL_REG 0x038
|
|
|
|
static inline void timer_reg_write_mem(int reg, u32 val)
|
|
{
|
|
switch (reg) {
|
|
case ARCH_TIMER_REG_CTRL:
|
|
__raw_writel(val, timer_base + QTIMER_CTRL_REG);
|
|
break;
|
|
case ARCH_TIMER_REG_TVAL:
|
|
__raw_writel(val, timer_base + QTIMER_CNTP_TVAL_REG);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static inline void timer_reg_write_cp15(int reg, u32 val)
|
|
{
|
|
switch (reg) {
|
|
case ARCH_TIMER_REG_CTRL:
|
|
asm volatile("mcr p15, 0, %0, c14, c2, 1" : : "r" (val));
|
|
break;
|
|
case ARCH_TIMER_REG_TVAL:
|
|
asm volatile("mcr p15, 0, %0, c14, c2, 0" : : "r" (val));
|
|
break;
|
|
}
|
|
|
|
isb();
|
|
}
|
|
|
|
static inline void arch_timer_reg_write(int cp15, int reg, u32 val)
|
|
{
|
|
if (cp15)
|
|
timer_reg_write_cp15(reg, val);
|
|
else
|
|
timer_reg_write_mem(reg, val);
|
|
}
|
|
|
|
static inline u32 timer_reg_read_mem(int reg)
|
|
{
|
|
u32 val;
|
|
|
|
switch (reg) {
|
|
case ARCH_TIMER_REG_CTRL:
|
|
val = __raw_readl(timer_base + QTIMER_CTRL_REG);
|
|
break;
|
|
case ARCH_TIMER_REG_FREQ:
|
|
val = __raw_readl(timer_base + QTIMER_FREQ_REG);
|
|
break;
|
|
case ARCH_TIMER_REG_TVAL:
|
|
val = __raw_readl(timer_base + QTIMER_CNTP_TVAL_REG);
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static inline u32 timer_reg_read_cp15(int reg)
|
|
{
|
|
u32 val;
|
|
|
|
switch (reg) {
|
|
case ARCH_TIMER_REG_CTRL:
|
|
asm volatile("mrc p15, 0, %0, c14, c2, 1" : "=r" (val));
|
|
break;
|
|
case ARCH_TIMER_REG_FREQ:
|
|
asm volatile("mrc p15, 0, %0, c14, c0, 0" : "=r" (val));
|
|
break;
|
|
case ARCH_TIMER_REG_TVAL:
|
|
asm volatile("mrc p15, 0, %0, c14, c2, 0" : "=r" (val));
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static inline u32 arch_timer_reg_read(int cp15, int reg)
|
|
{
|
|
if (cp15)
|
|
return timer_reg_read_cp15(reg);
|
|
else
|
|
return timer_reg_read_mem(reg);
|
|
}
|
|
|
|
static inline irqreturn_t arch_timer_handler(int cp15,
|
|
struct clock_event_device *evt)
|
|
{
|
|
unsigned long ctrl;
|
|
|
|
ctrl = arch_timer_reg_read(cp15, ARCH_TIMER_REG_CTRL);
|
|
if (ctrl & ARCH_TIMER_CTRL_IT_STAT) {
|
|
ctrl |= ARCH_TIMER_CTRL_IT_MASK;
|
|
arch_timer_reg_write(cp15, ARCH_TIMER_REG_CTRL, ctrl);
|
|
evt->event_handler(evt);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
static irqreturn_t arch_timer_handler_cp15(int irq, void *dev_id)
|
|
{
|
|
struct clock_event_device *evt = *(struct clock_event_device **)dev_id;
|
|
return arch_timer_handler(1, evt);
|
|
}
|
|
|
|
static irqreturn_t arch_timer_handler_mem(int irq, void *dev_id)
|
|
{
|
|
return arch_timer_handler(0, dev_id);
|
|
}
|
|
|
|
static inline void arch_timer_set_mode(int cp15, enum clock_event_mode mode,
|
|
struct clock_event_device *clk)
|
|
{
|
|
unsigned long ctrl;
|
|
|
|
switch (mode) {
|
|
case CLOCK_EVT_MODE_UNUSED:
|
|
case CLOCK_EVT_MODE_SHUTDOWN:
|
|
ctrl = arch_timer_reg_read(cp15, ARCH_TIMER_REG_CTRL);
|
|
ctrl &= ~ARCH_TIMER_CTRL_ENABLE;
|
|
arch_timer_reg_write(cp15, ARCH_TIMER_REG_CTRL, ctrl);
|
|
break;
|
|
case CLOCK_EVT_MODE_ONESHOT:
|
|
ctrl = arch_timer_reg_read(cp15, ARCH_TIMER_REG_CTRL);
|
|
ctrl |= ARCH_TIMER_CTRL_ENABLE;
|
|
arch_timer_reg_write(cp15, ARCH_TIMER_REG_CTRL, ctrl);
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void arch_timer_set_mode_cp15(enum clock_event_mode mode,
|
|
struct clock_event_device *clk)
|
|
{
|
|
arch_timer_set_mode(1, mode, clk);
|
|
}
|
|
|
|
static void arch_timer_set_mode_mem(enum clock_event_mode mode,
|
|
struct clock_event_device *clk)
|
|
{
|
|
arch_timer_set_mode(0, mode, clk);
|
|
}
|
|
|
|
static int arch_timer_set_next_event(int cp15, unsigned long evt,
|
|
struct clock_event_device *unused)
|
|
{
|
|
unsigned long ctrl;
|
|
|
|
ctrl = arch_timer_reg_read(cp15, ARCH_TIMER_REG_CTRL);
|
|
ctrl &= ~ARCH_TIMER_CTRL_IT_MASK;
|
|
arch_timer_reg_write(cp15, ARCH_TIMER_REG_CTRL, ctrl);
|
|
arch_timer_reg_write(cp15, ARCH_TIMER_REG_TVAL, evt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int arch_timer_set_next_event_cp15(unsigned long evt,
|
|
struct clock_event_device *unused)
|
|
{
|
|
return arch_timer_set_next_event(1, evt, unused);
|
|
}
|
|
|
|
static int arch_timer_set_next_event_mem(unsigned long evt,
|
|
struct clock_event_device *unused)
|
|
{
|
|
return arch_timer_set_next_event(0, evt, unused);
|
|
}
|
|
|
|
static int __cpuinit arch_timer_setup(struct clock_event_device *clk)
|
|
{
|
|
/* setup clock event only once for CPU 0 */
|
|
if (!smp_processor_id() && clk->irq == arch_timer_ppi)
|
|
return 0;
|
|
|
|
clk->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_C3STOP;
|
|
clk->name = "arch_sys_timer";
|
|
clk->rating = 450;
|
|
clk->set_mode = arch_timer_set_mode_cp15;
|
|
clk->set_next_event = arch_timer_set_next_event_cp15;
|
|
clk->irq = arch_timer_ppi;
|
|
|
|
/* Be safe... */
|
|
clk->set_mode(CLOCK_EVT_MODE_SHUTDOWN, clk);
|
|
|
|
clockevents_config_and_register(clk, arch_timer_rate,
|
|
0xf, 0x7fffffff);
|
|
|
|
*__this_cpu_ptr(arch_timer_evt) = clk;
|
|
|
|
enable_percpu_irq(clk->irq, 0);
|
|
if (arch_timer_ppi2)
|
|
enable_percpu_irq(arch_timer_ppi2, 0);
|
|
|
|
arch_counter_set_user_access();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Is the optional system timer available? */
|
|
static int local_timer_is_architected(void)
|
|
{
|
|
return (cpu_architecture() >= CPU_ARCH_ARMv7) &&
|
|
((read_cpuid_ext(CPUID_EXT_PFR1) >> 16) & 0xf) == 1;
|
|
}
|
|
|
|
static int arch_timer_available(void)
|
|
{
|
|
unsigned long freq;
|
|
|
|
if (arch_timer_rate == 0) {
|
|
arch_timer_reg_write(1, ARCH_TIMER_REG_CTRL, 0);
|
|
freq = arch_timer_reg_read(1, ARCH_TIMER_REG_FREQ);
|
|
|
|
/* Check the timer frequency. */
|
|
if (freq == 0) {
|
|
pr_warn("Architected timer frequency not available\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
arch_timer_rate = freq;
|
|
pr_info("Architected local timer running at %lu.%02luMHz.\n",
|
|
freq / 1000000, (freq / 10000) % 100);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline cycle_t notrace counter_get_cntpct_mem(void)
|
|
{
|
|
u32 cvall, cvalh, thigh;
|
|
|
|
do {
|
|
cvalh = __raw_readl(timer_base + QTIMER_CNTP_HIGH_REG);
|
|
cvall = __raw_readl(timer_base + QTIMER_CNTP_LOW_REG);
|
|
thigh = __raw_readl(timer_base + QTIMER_CNTP_HIGH_REG);
|
|
} while (cvalh != thigh);
|
|
|
|
return ((cycle_t) cvalh << 32) | cvall;
|
|
}
|
|
|
|
static inline cycle_t notrace counter_get_cntpct_cp15(void)
|
|
{
|
|
u32 cvall, cvalh;
|
|
|
|
asm volatile("mrrc p15, 0, %0, %1, c14" : "=r" (cvall), "=r" (cvalh));
|
|
return ((cycle_t) cvalh << 32) | cvall;
|
|
}
|
|
|
|
static inline cycle_t notrace counter_get_cntvct_mem(void)
|
|
{
|
|
u32 cvall, cvalh, thigh;
|
|
|
|
do {
|
|
cvalh = __raw_readl(timer_base + QTIMER_CNTV_HIGH_REG);
|
|
cvall = __raw_readl(timer_base + QTIMER_CNTV_LOW_REG);
|
|
thigh = __raw_readl(timer_base + QTIMER_CNTV_HIGH_REG);
|
|
} while (cvalh != thigh);
|
|
|
|
return ((cycle_t) cvalh << 32) | cvall;
|
|
}
|
|
|
|
static inline cycle_t notrace counter_get_cntvct_cp15(void)
|
|
{
|
|
u32 cvall, cvalh;
|
|
|
|
asm volatile("mrrc p15, 1, %0, %1, c14" : "=r" (cvall), "=r" (cvalh));
|
|
return ((cycle_t) cvalh << 32) | cvall;
|
|
}
|
|
|
|
static cycle_t (*get_cntpct_func)(void) = counter_get_cntpct_cp15;
|
|
static cycle_t (*get_cntvct_func)(void) = counter_get_cntvct_cp15;
|
|
|
|
cycle_t arch_counter_get_cntpct(void)
|
|
{
|
|
return get_cntpct_func();
|
|
}
|
|
EXPORT_SYMBOL(arch_counter_get_cntpct);
|
|
|
|
static cycle_t arch_counter_read(struct clocksource *cs)
|
|
{
|
|
return arch_counter_get_cntpct();
|
|
}
|
|
|
|
static unsigned long arch_timer_read_current_timer(void)
|
|
{
|
|
return arch_counter_get_cntpct();
|
|
}
|
|
|
|
static struct clocksource clocksource_counter = {
|
|
.name = "arch_sys_counter",
|
|
.rating = 400,
|
|
.read = arch_counter_read,
|
|
.mask = CLOCKSOURCE_MASK(56),
|
|
.flags = CLOCK_SOURCE_IS_CONTINUOUS | CLOCK_SOURCE_SUSPEND_NONSTOP,
|
|
};
|
|
|
|
static u32 arch_counter_get_cntvct32(void)
|
|
{
|
|
cycle_t cntvct;
|
|
|
|
cntvct = get_cntvct_func();
|
|
|
|
/*
|
|
* The sched_clock infrastructure only knows about counters
|
|
* with at most 32bits. Forget about the upper 24 bits for the
|
|
* time being...
|
|
*/
|
|
return (u32)(cntvct & (u32)~0);
|
|
}
|
|
|
|
static u64 notrace arch_timer_update_sched_clock(void)
|
|
{
|
|
return arch_counter_get_cntvct32();
|
|
}
|
|
|
|
static void __cpuinit arch_timer_stop(struct clock_event_device *clk)
|
|
{
|
|
pr_debug("arch_timer_teardown disable IRQ%d cpu #%d\n",
|
|
clk->irq, smp_processor_id());
|
|
disable_percpu_irq(clk->irq);
|
|
if (arch_timer_ppi2)
|
|
disable_percpu_irq(arch_timer_ppi2);
|
|
clk->set_mode(CLOCK_EVT_MODE_UNUSED, clk);
|
|
}
|
|
|
|
static struct local_timer_ops arch_timer_ops __cpuinitdata = {
|
|
.setup = arch_timer_setup,
|
|
.stop = arch_timer_stop,
|
|
};
|
|
|
|
static struct clock_event_device arch_timer_global_evt;
|
|
|
|
static void __init arch_timer_counter_init(void)
|
|
{
|
|
clocksource_register_hz(&clocksource_counter, arch_timer_rate);
|
|
|
|
sched_clock_register(arch_timer_update_sched_clock, 32, arch_timer_rate);
|
|
|
|
/* Use the architected timer for the delay loop. */
|
|
arch_delay_timer.read_current_timer = &arch_timer_read_current_timer;
|
|
arch_delay_timer.freq = arch_timer_rate;
|
|
register_current_timer_delay(&arch_delay_timer);
|
|
}
|
|
|
|
#ifdef CONFIG_CPU_PM
|
|
static unsigned int saved_cntkctl;
|
|
static int arch_timer_cpu_pm_notify(struct notifier_block *self,
|
|
unsigned long action, void *hcpu)
|
|
{
|
|
if (action == CPU_PM_ENTER)
|
|
saved_cntkctl = arch_timer_get_cntkctl();
|
|
else if (action == CPU_PM_ENTER_FAILED || action == CPU_PM_EXIT)
|
|
arch_timer_set_cntkctl(saved_cntkctl);
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block arch_timer_cpu_pm_notifier = {
|
|
.notifier_call = arch_timer_cpu_pm_notify,
|
|
};
|
|
|
|
static int __init arch_timer_cpu_pm_init(void)
|
|
{
|
|
return cpu_pm_register_notifier(&arch_timer_cpu_pm_notifier);
|
|
}
|
|
#else
|
|
static int __init arch_timer_cpu_pm_init(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int __init arch_timer_common_register(void)
|
|
{
|
|
int err;
|
|
|
|
if (!local_timer_is_architected())
|
|
return -ENXIO;
|
|
|
|
err = arch_timer_available();
|
|
if (err)
|
|
return err;
|
|
|
|
arch_timer_evt = alloc_percpu(struct clock_event_device *);
|
|
if (!arch_timer_evt)
|
|
return -ENOMEM;
|
|
|
|
err = request_percpu_irq(arch_timer_ppi, arch_timer_handler_cp15,
|
|
"arch_timer", arch_timer_evt);
|
|
if (err) {
|
|
pr_err("arch_timer: can't register interrupt %d (%d)\n",
|
|
arch_timer_ppi, err);
|
|
goto out_free;
|
|
}
|
|
|
|
if (arch_timer_ppi2) {
|
|
err = request_percpu_irq(arch_timer_ppi2,
|
|
arch_timer_handler_cp15,
|
|
"arch_timer", arch_timer_evt);
|
|
if (err) {
|
|
pr_err("arch_timer: can't register interrupt %d (%d)\n",
|
|
arch_timer_ppi2, err);
|
|
arch_timer_ppi2 = 0;
|
|
goto out_free_irq;
|
|
}
|
|
}
|
|
|
|
err = arch_timer_cpu_pm_init();
|
|
if (err)
|
|
goto out_free_irq;
|
|
|
|
err = local_timer_register(&arch_timer_ops);
|
|
if (err) {
|
|
/*
|
|
* We couldn't register as a local timer (could be
|
|
* because we're on a UP platform, or because some
|
|
* other local timer is already present...). Try as a
|
|
* global timer instead.
|
|
*/
|
|
arch_timer_global_evt.cpumask = cpumask_of(0);
|
|
err = arch_timer_setup(&arch_timer_global_evt);
|
|
}
|
|
|
|
if (err)
|
|
goto out_unreg_notify;
|
|
|
|
return 0;
|
|
|
|
out_unreg_notify:
|
|
cpu_pm_unregister_notifier(&arch_timer_cpu_pm_notifier);
|
|
out_free_irq:
|
|
free_percpu_irq(arch_timer_ppi, arch_timer_evt);
|
|
if (arch_timer_ppi2)
|
|
free_percpu_irq(arch_timer_ppi2, arch_timer_evt);
|
|
|
|
out_free:
|
|
free_percpu(arch_timer_evt);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int __init arch_timer_mem_register(void)
|
|
{
|
|
int err;
|
|
struct clock_event_device *clk;
|
|
|
|
clk = kzalloc(sizeof(*clk), GFP_KERNEL);
|
|
if (!clk)
|
|
return -ENOMEM;
|
|
|
|
clk->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_DYNIRQ;
|
|
clk->name = "arch_mem_timer";
|
|
clk->rating = 400;
|
|
clk->set_mode = arch_timer_set_mode_mem;
|
|
clk->set_next_event = arch_timer_set_next_event_mem;
|
|
clk->irq = arch_timer_spi;
|
|
clk->cpumask = cpu_all_mask;
|
|
|
|
clk->set_mode(CLOCK_EVT_MODE_SHUTDOWN, clk);
|
|
|
|
clockevents_config_and_register(clk, arch_timer_rate,
|
|
0xf, 0x7fffffff);
|
|
err = request_irq(arch_timer_spi, arch_timer_handler_mem,
|
|
IRQF_TIMER, "arch_timer", clk);
|
|
return err;
|
|
}
|
|
|
|
int __init arch_timer_register(struct arch_timer *at)
|
|
{
|
|
if (at->res[0].start <= 0 || !(at->res[0].flags & IORESOURCE_IRQ))
|
|
return -EINVAL;
|
|
|
|
arch_timer_ppi = at->res[0].start;
|
|
|
|
if (at->res[1].start > 0 && (at->res[1].flags & IORESOURCE_IRQ))
|
|
arch_timer_ppi2 = at->res[1].start;
|
|
|
|
if (at->res[2].start > 0 && at->res[2].end > 0 &&
|
|
(at->res[2].flags & IORESOURCE_MEM))
|
|
timer_base = ioremap(at->res[2].start,
|
|
resource_size(&at->res[2]));
|
|
|
|
if (!timer_base) {
|
|
pr_err("arch_timer: cant map timer base\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return arch_timer_common_register();
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id arch_timer_of_match[] __initconst = {
|
|
{ .compatible = "arm,armv7-timer", },
|
|
{},
|
|
};
|
|
|
|
static const struct of_device_id arch_timer_mem_of_match[] __initconst = {
|
|
{ .compatible = "arm,armv7-timer-mem", },
|
|
{},
|
|
};
|
|
|
|
int __init arch_timer_of_register(void)
|
|
{
|
|
struct device_node *np, *frame;
|
|
u32 freq;
|
|
int ret;
|
|
int has_cp15 = false, has_mem = false;
|
|
|
|
np = of_find_matching_node(NULL, arch_timer_of_match);
|
|
if (np) {
|
|
has_cp15 = true;
|
|
/*
|
|
* Try to determine the frequency from the device tree
|
|
*/
|
|
if (!of_property_read_u32(np, "clock-frequency", &freq))
|
|
arch_timer_rate = freq;
|
|
|
|
ret = irq_of_parse_and_map(np, 0);
|
|
if (ret <= 0) {
|
|
pr_err("arch_timer: interrupt not specified in timer node\n");
|
|
return -ENODEV;
|
|
}
|
|
arch_timer_ppi = ret;
|
|
ret = irq_of_parse_and_map(np, 1);
|
|
if (ret > 0)
|
|
arch_timer_ppi2 = ret;
|
|
|
|
ret = arch_timer_common_register();
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
np = of_find_matching_node(NULL, arch_timer_mem_of_match);
|
|
if (np) {
|
|
has_mem = true;
|
|
|
|
if (!has_cp15) {
|
|
get_cntpct_func = counter_get_cntpct_mem;
|
|
get_cntvct_func = counter_get_cntvct_mem;
|
|
}
|
|
/*
|
|
* Try to determine the frequency from the device tree
|
|
*/
|
|
if (!of_property_read_u32(np, "clock-frequency", &freq))
|
|
arch_timer_rate = freq;
|
|
|
|
frame = of_get_next_child(np, NULL);
|
|
if (!frame) {
|
|
pr_err("arch_timer: no child frame\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
timer_base = of_iomap(frame, 0);
|
|
if (!timer_base) {
|
|
pr_err("arch_timer: cant map timer base\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
arch_timer_spi = irq_of_parse_and_map(frame, 0);
|
|
if (!arch_timer_spi) {
|
|
pr_err("arch_timer: no physical timer irq\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = arch_timer_mem_register();
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (!has_cp15 && !has_mem) {
|
|
pr_err("arch_timer: can't find DT node\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
arch_timer_counter_init();
|
|
|
|
return 0;
|
|
}
|
|
#endif
|