[PATCH v2 RESEND 2/2] ARM: local timers: add timer support using IO mapped register

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The current arch_timer only support accessing through CP15 interface.
Add support for ARM processors that only support IO mapped register
interface. The memory mapped timer interface works with SPI
interrupts instead of PPI.

Signed-off-by: Rohit Vaswani <rvaswani@xxxxxxxxxxxxxx>
---
 .../devicetree/bindings/arm/arch_timer.txt         |    9 +-
 arch/arm/kernel/arch_timer.c                       |  299 +++++++++++++++++++-
 2 files changed, 297 insertions(+), 11 deletions(-)

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

--
To unsubscribe from this list: send the line "unsubscribe linux-doc" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Linux FS]     [Yosemite Forum]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux