[RFC 2/8] ARM:global_timer: Add ARM global timer support.
Rob Herring
robherring2 at gmail.com
Wed May 8 10:26:33 EDT 2013
On 05/08/2013 09:11 AM, Srinivas KANDAGATLA wrote:
> From: Stuart Menefy <stuart.menefy at st.com>
>
> This is a simple driver for the global timer module found in the Cortex
> A9-MP cores from revision r1p0 onwards. This should be able to perform
> the functions of the system timer and the local timer in an SMP system.
>
> The global timer has the following features:
> The global timer is a 64-bit incrementing counter with an
> auto-incrementing feature. It continues incrementing after sending
> interrupts. The global timer is memory mapped in the private memory
> region.
> The global timer is accessible to all Cortex-A9 processors in the
> cluster. Each Cortex-A9 processor has a private 64-bit comparator that
> is used to assert a private interrupt when the global timer has reached
> the comparator value. All the Cortex-A9 processors in a design use the
> banked ID, ID27, for this interrupt. ID27 is sent to the Interrupt
> Controller as a Private Peripheral Interrupt. The global timer is
> clocked by PERIPHCLK.
PERIPHCLK scales with cpu clock typically so the global timer is not a
suitable clocksource if you use cpufreq. You can deal with the scaling
on clockevents, but not a clocksource. I imagine the global timers is
also typically power-gated in some low power modes. This is why no one
has added support already. Are neither of those an issue or going to be
an issue for you?
Rob
>
> Signed-off-by: Stuart Menefy <stuart.menefy at st.com>
> Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla at st.com>
> ---
> Documentation/devicetree/bindings/arm/gt.txt | 21 ++
> arch/arm/Kconfig | 6 +
> arch/arm/include/asm/global_timer.h | 12 +
> arch/arm/kernel/Makefile | 1 +
> arch/arm/kernel/global_timer.c | 325 ++++++++++++++++++++++++++
> 5 files changed, 365 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/devicetree/bindings/arm/gt.txt
> create mode 100644 arch/arm/include/asm/global_timer.h
> create mode 100644 arch/arm/kernel/global_timer.c
>
> diff --git a/Documentation/devicetree/bindings/arm/gt.txt b/Documentation/devicetree/bindings/arm/gt.txt
> new file mode 100644
> index 0000000..4ec5fb0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/gt.txt
> @@ -0,0 +1,21 @@
> +
> +* ARM Global Timer
> + Cortex-A9 are often associated with a per-core Global timer.
> +
> +** Timer node required properties:
> +
> +- compatible : Should be one of:
> + "arm,cortex-a9-global-timer"
> +
> +- interrupts : One interrupt to each core
> +
> +- reg : Specify the base address and the size of the GT timer
> + register window.
> +
> +Example:
> +
> + gt-timer at 2c000600 {
> + compatible = "arm,cortex-a9-global-timer";
> + reg = <0x2c000600 0x20>;
> + interrupts = <1 13 0xf01>;
> + };
> diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
> index 1cacda4..c8c524e 100644
> --- a/arch/arm/Kconfig
> +++ b/arch/arm/Kconfig
> @@ -1606,6 +1606,12 @@ config HAVE_ARM_TWD
> help
> This options enables support for the ARM timer and watchdog unit
>
> +config HAVE_ARM_GT
> + bool
> + select CLKSRC_OF if OF
> + help
> + This options enables support for the ARM global timer unit
> +
> choice
> prompt "Memory split"
> default VMSPLIT_3G
> diff --git a/arch/arm/include/asm/global_timer.h b/arch/arm/include/asm/global_timer.h
> new file mode 100644
> index 0000000..46f9188
> --- /dev/null
> +++ b/arch/arm/include/asm/global_timer.h
> @@ -0,0 +1,12 @@
> +/*
> + * arch/arm/include/asm/global_timer.h
> + *
> + * Copyright (C) 2013 STMicroelectronics (R&D) Limited.
> + * Author: Stuart Menefy <stuart.menefy at st.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.
> + */
> +
> +int __init global_timer_init(void __iomem *base, unsigned int timer_irq);
> diff --git a/arch/arm/kernel/Makefile b/arch/arm/kernel/Makefile
> index 5f3338e..af51808 100644
> --- a/arch/arm/kernel/Makefile
> +++ b/arch/arm/kernel/Makefile
> @@ -35,6 +35,7 @@ obj-$(CONFIG_ARM_CPU_SUSPEND) += sleep.o suspend.o
> obj-$(CONFIG_SMP) += smp.o smp_tlb.o
> obj-$(CONFIG_HAVE_ARM_SCU) += smp_scu.o
> obj-$(CONFIG_HAVE_ARM_TWD) += smp_twd.o
> +obj-$(CONFIG_HAVE_ARM_GT) += global_timer.o
> obj-$(CONFIG_ARM_ARCH_TIMER) += arch_timer.o
> obj-$(CONFIG_DYNAMIC_FTRACE) += ftrace.o insn.o
> obj-$(CONFIG_FUNCTION_GRAPH_TRACER) += ftrace.o insn.o
> diff --git a/arch/arm/kernel/global_timer.c b/arch/arm/kernel/global_timer.c
> new file mode 100644
> index 0000000..0ab1af3
> --- /dev/null
> +++ b/arch/arm/kernel/global_timer.c
> @@ -0,0 +1,325 @@
> +/*
> + * linux/arch/arm/kernel/global_timer.c
> + *
> + * Copyright (C) 2013 STMicroelectronics (R&D) Limited.
> + * Author: Stuart Menefy <stuart.menefy at st.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/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/clocksource.h>
> +#include <linux/clockchips.h>
> +#include <linux/clk.h>
> +#include <linux/clkdev.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_address.h>
> +
> +#include <asm/mach/irq.h>
> +#include <asm/global_timer.h>
> +#include <asm/localtimer.h>
> +
> +#define GT_COUNTER0 0x00
> +#define GT_COUNTER1 0x04
> +
> +#define GT_CONTROL 0x08
> +#define GT_CONTROL_TIMER_ENABLE BIT(0)
> +#define GT_CONTROL_COMP_ENABLE BIT(1) /* banked */
> +#define GT_CONTROL_IRQ_ENABLE BIT(2) /* banked */
> +#define GT_CONTROL_AUTO_INC BIT(3) /* banked */
> +
> +#define GT_INT_STATUS 0x0c
> +#define GT_INT_STATUS_EVENT_FLAG BIT(0)
> +
> +#define GT_COMP0 0x10
> +#define GT_COMP1 0x14
> +#define GT_AUTO_INC 0x18
> +
> +/*
> + * We are expecting to be clocked by the ARM peripheral clock.
> + *
> + * Note: it is assumed we are using a prescaler value of zero, so this is
> + * the units for all operations.
> + */
> +static void __iomem *gt_base;
> +static struct clk *gt_clk;
> +static unsigned long gt_clk_rate;
> +static int gt_ppi;
> +static struct clock_event_device __percpu **gt_evt;
> +static DEFINE_PER_CPU(bool, percpu_init_called);
> +static DEFINE_PER_CPU(struct clock_event_device, gt_clockevent);
> +
> +union gt_counter {
> + cycle_t cycles;
> + struct {
> + uint32_t lower;
> + uint32_t upper;
> + };
> +};
> +
> +static union gt_counter gt_counter_read(void)
> +{
> + union gt_counter res;
> + uint32_t upper;
> +
> + upper = readl(gt_base + GT_COUNTER1);
> + do {
> + res.upper = upper;
> + res.lower = readl(gt_base + GT_COUNTER0);
> + upper = readl(gt_base + GT_COUNTER1);
> + } while (upper != res.upper);
> +
> + return res;
> +}
> +
> +static void gt_compare_set(unsigned long delta, int periodic)
> +{
> + union gt_counter counter = gt_counter_read();
> + unsigned long ctrl = readl(gt_base + GT_CONTROL);
> +
> + BUG_ON(!(ctrl & GT_CONTROL_TIMER_ENABLE));
> + BUG_ON(ctrl & (GT_CONTROL_COMP_ENABLE |
> + GT_CONTROL_IRQ_ENABLE |
> + GT_CONTROL_AUTO_INC));
> +
> + counter.cycles += delta;
> + writel(counter.lower, gt_base + GT_COMP0);
> + writel(counter.upper, gt_base + GT_COMP1);
> +
> + ctrl |= GT_CONTROL_COMP_ENABLE | GT_CONTROL_IRQ_ENABLE;
> +
> + if (periodic) {
> + writel(delta, gt_base + GT_AUTO_INC);
> + ctrl |= GT_CONTROL_AUTO_INC;
> + }
> +
> + writel(ctrl, gt_base + GT_CONTROL);
> +}
> +
> +static void gt_clockevent_set_mode(enum clock_event_mode mode,
> + struct clock_event_device *clk)
> +{
> + switch (mode) {
> + case CLOCK_EVT_MODE_PERIODIC:
> + gt_compare_set(gt_clk_rate/HZ, 1);
> + break;
> + case CLOCK_EVT_MODE_ONESHOT:
> + /* period set, and timer enabled in 'next_event' hook */
> + BUG_ON(readl(gt_base + GT_CONTROL) &
> + (GT_CONTROL_COMP_ENABLE |
> + GT_CONTROL_IRQ_ENABLE |
> + GT_CONTROL_AUTO_INC));
> + /* Fall through */
> + case CLOCK_EVT_MODE_UNUSED:
> + case CLOCK_EVT_MODE_SHUTDOWN:
> + default:
> + writel(GT_CONTROL_TIMER_ENABLE, gt_base + GT_CONTROL);
> + break;
> + }
> +}
> +
> +static int gt_clockevent_set_next_event(unsigned long evt,
> + struct clock_event_device *unused)
> +{
> + gt_compare_set(evt, 0);
> + return 0;
> +}
> +
> +static irqreturn_t gt_clockevent_interrupt(int irq, void *dev_id)
> +{
> + struct clock_event_device *evt = *(struct clock_event_device **)dev_id;
> +
> + writel(GT_INT_STATUS_EVENT_FLAG, gt_base + GT_INT_STATUS);
> + evt->event_handler(evt);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int __cpuinit gt_clockevents_init(struct clock_event_device *clk)
> +{
> + struct clock_event_device **this_cpu_clk;
> + int cpu = smp_processor_id();
> +
> + clk->name = "Global Timer CE";
> + clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
> + clk->set_mode = gt_clockevent_set_mode;
> + clk->set_next_event = gt_clockevent_set_next_event;
> + this_cpu_clk = __this_cpu_ptr(gt_evt);
> + *this_cpu_clk = clk;
> + clk->irq = gt_ppi;
> + clockevents_config_and_register(clk, gt_clk_rate,
> + 0xf, 0xffffffff);
> + per_cpu(percpu_init_called, cpu) = true;
> + enable_percpu_irq(clk->irq, IRQ_TYPE_NONE);
> + return 0;
> +}
> +
> +static void gt_clockevents_stop(struct clock_event_device *clk)
> +{
> + gt_clockevent_set_mode(CLOCK_EVT_MODE_UNUSED, clk);
> + disable_percpu_irq(clk->irq);
> +}
> +
> +static int __cpuinit gt_clockevents_setup(struct clock_event_device *clk)
> +{
> + int cpu = smp_processor_id();
> +
> + /* Use existing clock_event for boot cpu */
> + if (per_cpu(percpu_init_called, cpu))
> + return 0;
> +
> + writel(0, gt_base + GT_CONTROL);
> + writel(0, gt_base + GT_COUNTER0);
> + writel(0, gt_base + GT_COUNTER1);
> + writel(GT_CONTROL_TIMER_ENABLE, gt_base + GT_CONTROL);
> +
> + return gt_clockevents_init(clk);
> +}
> +
> +static cycle_t gt_clocksource_read(struct clocksource *cs)
> +{
> + union gt_counter res = gt_counter_read();
> + return res.cycles;
> +}
> +
> +static struct clocksource gt_clocksource = {
> + .name = "Global Timer CS",
> + .rating = 300,
> + .read = gt_clocksource_read,
> + .mask = CLOCKSOURCE_MASK(64),
> + .flags = CLOCK_SOURCE_IS_CONTINUOUS,
> +};
> +
> +static void __init gt_clocksource_init(void)
> +{
> + writel(0, gt_base + GT_CONTROL);
> + writel(0, gt_base + GT_COUNTER0);
> + writel(0, gt_base + GT_COUNTER1);
> + writel(GT_CONTROL_TIMER_ENABLE, gt_base + GT_CONTROL);
> +
> + gt_clocksource.shift = 20;
> + gt_clocksource.mult =
> + clocksource_hz2mult(gt_clk_rate, gt_clocksource.shift);
> + clocksource_register(>_clocksource);
> +}
> +
> +static struct clk *gt_get_clock(void)
> +{
> + struct clk *clk;
> + int err;
> +
> + clk = clk_get_sys("gt", NULL);
> + if (IS_ERR(clk)) {
> + pr_err("global-timer: clock not found: %ld\n", PTR_ERR(clk));
> + return clk;
> + }
> +
> + err = clk_prepare_enable(clk);
> + if (err) {
> + pr_err("global-timer: clock prepare+enable failed: %d\n", err);
> + clk_put(clk);
> + return ERR_PTR(err);
> + }
> +
> + return clk;
> +}
> +
> +static struct local_timer_ops gt_lt_ops __cpuinitdata = {
> + .setup = gt_clockevents_setup,
> + .stop = gt_clockevents_stop,
> +};
> +
> +int __init global_timer_init(void __iomem *base, unsigned int timer_irq)
> +{
> + unsigned int cpu = smp_processor_id();
> + struct clock_event_device *evt = &per_cpu(gt_clockevent, cpu);
> + int err = 0;
> +
> + if (gt_base) {
> + pr_warn("global-timer: invalid base address\n");
> + return -EINVAL;
> + }
> +
> + gt_clk = gt_get_clock();
> + if (IS_ERR(gt_clk)) {
> + pr_warn("global-timer: clk not found\n");
> + return -EINVAL;
> + }
> +
> + gt_evt = alloc_percpu(struct clock_event_device *);
> + if (!gt_evt) {
> + pr_warn("global-timer: can't allocate memory\n");
> + return -ENOMEM;
> + }
> +
> + err = request_percpu_irq(timer_irq, gt_clockevent_interrupt,
> + "gt", gt_evt);
> + if (err) {
> + pr_warn("global-timer: can't register interrupt %d (%d)\n",
> + timer_irq, err);
> + goto out_free;
> + }
> +
> + gt_base = base;
> + gt_clk_rate = clk_get_rate(gt_clk);
> +
> + evt->irq = timer_irq;
> + gt_ppi = timer_irq;
> + evt->cpumask = cpumask_of(cpu);
> +
> + gt_clocksource_init();
> + gt_clockevents_init(evt);
> + err = local_timer_register(>_lt_ops);
> + if (err) {
> + pr_warn("global-timer: unable to register local timer.\n");
> + goto out_irq;
> + }
> +
> + return 0;
> +
> +out_irq:
> + free_percpu_irq(timer_irq, gt_evt);
> +out_free:
> + free_percpu(gt_evt);
> + return err;
> +}
> +
> +#ifdef CONFIG_OF
> +static void __init global_timer_of_register(struct device_node *np)
> +{
> + struct clk *clk;
> + int err = 0;
> + int gt_ppi;
> + static void __iomem *gt_base;
> +
> + gt_ppi = irq_of_parse_and_map(np, 0);
> + if (!gt_ppi) {
> + err = -EINVAL;
> + goto out;
> + }
> +
> + gt_base = of_iomap(np, 0);
> + if (!gt_base) {
> + err = -ENOMEM;
> + goto out;
> + }
> +
> + clk = of_clk_get(np, 0);
> + if (!IS_ERR(clk))
> + clk_register_clkdev(clk, NULL, "gt");
> +
> + global_timer_init(gt_base, gt_ppi);
> +
> +out:
> + WARN(err, "Global timer register failed (%d)\n", err);
> +}
> +
> +CLOCKSOURCE_OF_DECLARE(arm_gt_a9, "arm,cortex-a9-global-timer",
> + global_timer_of_register);
> +#endif
>
More information about the linux-arm-kernel
mailing list