diff --git a/Documentation/devicetree/bindings/arm/arch_timer.txt b/Documentation/devicetree/bindings/arm/arch_timer.txt
index 52478c8..8e01328 100644
--- a/Documentation/devicetree/bindings/arm/arch_timer.txt
+++ b/Documentation/devicetree/bindings/arm/arch_timer.txt
@@ -7,10 +7,13 @@ The timer is attached to a GIC to deliver its per-processor interrupts.
** Timer node properties:
-- compatible : Should at least contain "arm,armv7-timer".
+- compatible : Should at least contain "arm,armv7-timer" or
+ "arm,armv7-timer-mem" if using the memory mapped arch timer interface.
-- interrupts : Interrupt list for secure, non-secure, virtual and
- hypervisor timers, in that order.
+- interrupts : If using the cp15 interface, the interrupt list for secure,
+ non-secure, virtual and hypervisor timers, in that order.
+ If using the memory mapped interface, list the interrupts for each core,
+ starting with core 0.
- clock-frequency : The frequency of the main counter, in Hz. Optional.
diff --git a/arch/arm/kernel/arch_timer.c b/arch/arm/kernel/arch_timer.c
index 8672a75..f79092d 100644
--- a/arch/arm/kernel/arch_timer.c
+++ b/arch/arm/kernel/arch_timer.c
@@ -17,7 +17,9 @@
#include <linux/jiffies.h>
#include <linux/clockchips.h>
#include <linux/interrupt.h>
+#include <linux/irq.h>
#include <linux/of_irq.h>
+#include <linux/of_address.h>
#include <linux/io.h>
#include <asm/cputype.h>
@@ -44,6 +46,11 @@ extern void init_current_timer_delay(unsigned long freq);
static bool arch_timer_use_virtual = true;
+static bool arch_timer_irq_percpu = true;
+static void __iomem *timer_base;
+static unsigned arch_timer_mem_irqs[NR_CPUS];
+static unsigned arch_timer_num_irqs;
+
/*
* Architected system timer support.
*/
@@ -56,8 +63,17 @@ static bool arch_timer_use_virtual = true;
#define ARCH_TIMER_REG_FREQ 1
#define ARCH_TIMER_REG_TVAL 2
+/* Iomapped Register Offsets */
+static unsigned arch_timer_mem_offset[] = {0x2C, 0x10, 0x28};
+#define ARCH_TIMER_CNTP_LOW_REG 0x0
+#define ARCH_TIMER_CNTP_HIGH_REG 0x4
+#define ARCH_TIMER_CNTV_LOW_REG 0x8
+#define ARCH_TIMER_CNTV_HIGH_REG 0xC
+
#define ARCH_TIMER_PHYS_ACCESS 0
#define ARCH_TIMER_VIRT_ACCESS 1
+#define ARCH_TIMER_MEM_PHYS_ACCESS 2
+#define ARCH_TIMER_MEM_VIRT_ACCESS 3
/*
* These register accessors are marked inline so the compiler can@@ -88,6 +104,9 @@ static inline void arch_timer_reg_write(const int access, const int reg, u32 val
}
}
+ if (access == ARCH_TIMER_MEM_PHYS_ACCESS)
+ __raw_writel(val, timer_base + arch_timer_mem_offset[reg]);
+
isb();
}
@@ -120,12 +139,16 @@ static inline u32 arch_timer_reg_read(const int access, const int reg)
}
}
+ if (access == ARCH_TIMER_MEM_PHYS_ACCESS)
+ val = __raw_readl(timer_base + arch_timer_mem_offset[reg]);
+
return val;
}
static inline cycle_t arch_timer_counter_read(const int access)
{
cycle_t cval = 0;
+ u32 cvall, cvalh, thigh;
if (access == ARCH_TIMER_PHYS_ACCESS)
asm volatile("mrrc p15, 0, %Q0, %R0, c14" : "=r" (cval));@@ -133,17 +156,49 @@ static inline cycle_t arch_timer_counter_read(const int access)
if (access == ARCH_TIMER_VIRT_ACCESS)
asm volatile("mrrc p15, 1, %Q0, %R0, c14" : "=r" (cval));
+ if (access == ARCH_TIMER_MEM_PHYS_ACCESS) {
+ do {
+ cvalh = __raw_readl(timer_base +
+ ARCH_TIMER_CNTP_HIGH_REG);
+ cvall = __raw_readl(timer_base +
+ ARCH_TIMER_CNTP_LOW_REG);
+ thigh = __raw_readl(timer_base +
+ ARCH_TIMER_CNTP_HIGH_REG);
+ } while (cvalh != thigh);
+
+ cval = ((cycle_t) cvalh << 32) | cvall;
+ }
+
+ if (access == ARCH_TIMER_MEM_VIRT_ACCESS) {
+ do {
+ cvalh = __raw_readl(timer_base +
+ ARCH_TIMER_CNTV_HIGH_REG);
+ cvall = __raw_readl(timer_base +
+ ARCH_TIMER_CNTV_LOW_REG);
+ thigh = __raw_readl(timer_base +
+ ARCH_TIMER_CNTV_HIGH_REG);
+ } while (cvalh != thigh);
+
+ cval = ((cycle_t) cvalh << 32) | cvall;
+ }
+
return cval;
}
static inline cycle_t arch_counter_get_cntpct(void)
{
- return arch_timer_counter_read(ARCH_TIMER_PHYS_ACCESS);
+ if (timer_base)
+ return arch_timer_counter_read(ARCH_TIMER_MEM_PHYS_ACCESS);
+ else
+ return arch_timer_counter_read(ARCH_TIMER_PHYS_ACCESS);
}
static inline cycle_t arch_counter_get_cntvct(void)
{
- return arch_timer_counter_read(ARCH_TIMER_VIRT_ACCESS);
+ if (timer_base)
+ return arch_timer_counter_read(ARCH_TIMER_MEM_VIRT_ACCESS);
+ else
+ return arch_timer_counter_read(ARCH_TIMER_VIRT_ACCESS);
}
static irqreturn_t inline timer_handler(const int access,@@ -175,6 +230,13 @@ static irqreturn_t arch_timer_handler_phys(int irq, void *dev_id)
return timer_handler(ARCH_TIMER_PHYS_ACCESS, evt);
}
+static irqreturn_t arch_timer_handler_mem(int irq, void *dev_id)
+{
+ struct clock_event_device *evt = *(struct clock_event_device **)dev_id;
+
+ return timer_handler(ARCH_TIMER_MEM_PHYS_ACCESS, evt);
+}
+
static inline void timer_set_mode(const int access, int mode)
{
unsigned long ctrl;@@ -202,6 +264,12 @@ static void arch_timer_set_mode_phys(enum clock_event_mode mode,
timer_set_mode(ARCH_TIMER_PHYS_ACCESS, mode);
}
+static void arch_timer_set_mode_mem(enum clock_event_mode mode,
+ struct clock_event_device *clk)
+{
+ timer_set_mode(ARCH_TIMER_MEM_PHYS_ACCESS, mode);
+}
+
static inline void set_next_event(const int access, unsigned long evt)
{
unsigned long ctrl;@@ -227,8 +295,41 @@ static int arch_timer_set_next_event_phys(unsigned long evt,
return 0;
}
+static int arch_timer_set_next_event_mem(unsigned long evt,
+ struct clock_event_device *unused)
+{
+ set_next_event(ARCH_TIMER_MEM_PHYS_ACCESS, evt);
+ return 0;
+}
+
+static int __cpuinit arch_timer_mem_setup(struct clock_event_device *clk)
+{
+ unsigned cpu = smp_processor_id();
+
+ clk->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_C3STOP;
+ clk->name = "arch_sys_timer";
+ clk->rating = 450;
+ clk->irq = arch_timer_mem_irqs[cpu];
+ clk->set_mode = arch_timer_set_mode_mem;
+ clk->set_next_event = arch_timer_set_next_event_mem;
+
+ clk->set_mode(CLOCK_EVT_MODE_SHUTDOWN, NULL);
+
+ clockevents_config_and_register(clk, arch_timer_rate,
+ 0xf, 0x7fffffff);
+
+ *__this_cpu_ptr(arch_timer_evt) = clk;
+ if (arch_timer_irq_percpu)
+ enable_percpu_irq(arch_timer_mem_irqs[cpu], 0);
+ else
+ enable_irq(arch_timer_mem_irqs[cpu]);
+
+ return 0;
+}
+
static int __cpuinit arch_timer_setup(struct clock_event_device *clk)
{
+
clk->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_C3STOP;
clk->name = "arch_sys_timer";
clk->rating = 450;@@ -271,11 +372,15 @@ static int arch_timer_available(void)
{
unsigned long freq;
- if (!local_timer_is_architected())
+ if (!timer_base && !local_timer_is_architected())
return -ENXIO;
if (arch_timer_rate == 0) {
- freq = arch_timer_reg_read(ARCH_TIMER_PHYS_ACCESS,
+ if (timer_base)
+ freq = arch_timer_reg_read(ARCH_TIMER_MEM_PHYS_ACCESS,
+ ARCH_TIMER_REG_FREQ);
+ else
+ freq = arch_timer_reg_read(ARCH_TIMER_PHYS_ACCESS,
ARCH_TIMER_REG_FREQ);
/* Check the timer frequency. */@@ -363,6 +468,19 @@ struct timecounter *arch_timer_get_timecounter(void)
return &timecounter;
}
+static void __cpuinit arch_timer_mem_stop(struct clock_event_device *clk)
+{
+ pr_debug("%s disable IRQ%d cpu #%d\n", __func__, clk->irq,
+ smp_processor_id());
+
+ if (arch_timer_irq_percpu)
+ disable_percpu_irq(clk->irq);
+ else
+ disable_irq(clk->irq);
+
+ clk->set_mode(CLOCK_EVT_MODE_UNUSED, clk);
+}
+
static void __cpuinit arch_timer_stop(struct clock_event_device *clk)
{
pr_debug("arch_timer_teardown disable IRQ%d cpu #%d\n",@@ -384,6 +502,11 @@ static struct local_timer_ops arch_timer_ops __cpuinitdata = {
.stop = arch_timer_stop,
};
+static struct local_timer_ops arch_timer_mem_ops __cpuinitdata = {
+ .setup = arch_timer_mem_setup,
+ .stop = arch_timer_mem_stop,
+};
+
static struct clock_event_device arch_timer_global_evt;
static int __init arch_timer_register(void)@@ -466,11 +589,166 @@ out:
return err;
}
+static int __init arch_timer_mem_register(void)
+{
+ int err, irq, i;
+
+ err = arch_timer_available();
+ if (err)
+ goto out;
+
+ arch_timer_evt = alloc_percpu(struct clock_event_device *);
+ if (!arch_timer_evt) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ clocksource_register_hz(&clocksource_counter, arch_timer_rate);
+
+ if (arch_timer_irq_percpu) {
+ for (i = 0; i < arch_timer_num_irqs; i++) {
+ irq = arch_timer_mem_irqs[i];
+ err = request_percpu_irq(irq, arch_timer_handler_mem,
+ "arch_timer", arch_timer_evt);
+ }
+ } else {
+ for (i = 0; i < arch_timer_num_irqs; i++) {
+ irq = arch_timer_mem_irqs[i];
+ err = request_irq(irq, arch_timer_handler_mem, 0,
+ "arch_timer",
+ per_cpu_ptr(arch_timer_evt, i));
+ /* Disable irq now and it will be enabled later
+ * in arch_timer_mem_setup which is called from
+ * smp code. If we don't disable it here, then we
+ * face unbalanced irq problem in arch_timer_mem_setup.
+ * Percpu irqs don't have irq depth management,
+ * hence they dont face this problem.
+ */
+ disable_irq(irq);
+ }
+ }
+
+ if (err) {
+ pr_err("arch_timer_mem: can't register interrupt %d (%d)\n",
+ irq, err);
+ goto out_free;
+ }
+
+ err = local_timer_register(&arch_timer_mem_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);
+ }
+
+ percpu_timer_setup();
+
+ if (err)
+ goto out_free_irq;
+
+ return 0;
+
+out_free_irq:
+ if (arch_timer_irq_percpu)
+ for (i = 0; i < arch_timer_num_irqs; i++)
+ free_percpu_irq(arch_timer_mem_irqs[i], arch_timer_evt);
+ else
+ for (i = 0; i < arch_timer_num_irqs; i++)
+ free_irq(arch_timer_mem_irqs[i],
+ per_cpu(arch_timer_evt, i));
+
+out_free:
+ free_percpu(arch_timer_evt);
+out:
+ return err;
+}
+
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",},
+ {},
+};
+
+static inline int __init arch_timer_base_init(void)
+{
+ struct device_node *np;
+
+ if (!timer_base) {
+ if (!of_find_matching_node(NULL, arch_timer_of_match)) {
+ np = of_find_matching_node(NULL,
+ arch_timer_mem_of_match);
+ if (!np) {
+ pr_err("arch_timer: can't find armv7-timer-mem DT node\n");
+ return -ENODEV;
+ }
+
+ if (of_get_address(np, 0, NULL, NULL)) {
+ timer_base = of_iomap(np, 0);
+ if (!timer_base) {
+ pr_err("arch_timer: cant map timer base\n");
+ return -ENOMEM;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static inline void __init arch_timer_base_free(void)
+{
+ if (timer_base)
+ iounmap(timer_base);
+}
+
+static int __init arch_timer_mem_of_register(void)
+{
+ struct device_node *np;
+ u32 freq;
+ int i, ret, irq;
+ arch_timer_num_irqs = num_possible_cpus();
+
+ np = of_find_matching_node(NULL, arch_timer_mem_of_match);
+ if (!np) {
+ pr_err("arch_timer: can't find armv7-timer-mem DT node\n");
+ return -ENODEV;
+ }
+
+ arch_timer_use_virtual = false;
+
+ /* Try to determine the frequency from the device tree or CNTFRQ */
+ if (!of_property_read_u32(np, "clock-frequency", &freq))
+ arch_timer_rate = freq;
+
+ for (i = 0; i < arch_timer_num_irqs; i++) {
+ arch_timer_mem_irqs[i] = irq = irq_of_parse_and_map(np, i);
+ if (!irq)
+ break;
+ }
+
+ if (!irq_is_per_cpu(arch_timer_ppi[0]))
+ arch_timer_irq_percpu = false;
+
+ ret = arch_timer_base_init();
+ if (ret)
+ return ret;
+
+ ret = arch_timer_mem_register();
+ if (ret)
+ arch_timer_base_free();
+
+ return ret;
+}
+
int __init arch_timer_of_register(void)
{
struct device_node *np;@@ -479,8 +757,8 @@ int __init arch_timer_of_register(void)
np = of_find_matching_node(NULL, arch_timer_of_match);
if (!np) {
- pr_err("arch_timer: can't find DT node\n");
- return -ENODEV;
+ pr_debug("arch_timer: can't find DT node for armv7-timer, falling back to memory mapped arch timer\n");
+ return arch_timer_mem_of_register();
}
/* Try to determine the frequency from the device tree or CNTFRQ */@@ -496,7 +774,6 @@ int __init arch_timer_of_register(void)
*/
if (!arch_timer_ppi[VIRT_PPI]) {
arch_timer_use_virtual = false;
-
if (!arch_timer_ppi[PHYS_SECURE_PPI] ||
!arch_timer_ppi[PHYS_NONSECURE_PPI]) {
pr_warn("arch_timer: No interrupt available, giving up\n");@@ -512,10 +789,16 @@ int __init arch_timer_sched_clock_init(void)
u32 (*cnt32)(void);
int err;
- err = arch_timer_available();
+ err = arch_timer_base_init();
if (err)
return err;
+ err = arch_timer_available();
+ if (err) {
+ arch_timer_base_free();
+ return err;
+ }
+
if (arch_timer_use_virtual)
cnt32 = arch_counter_get_cntvct32;
else--
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation