linux/drivers/clocksource/time-armada-370-xp.c
<<
>>
Prefs
   1/*
   2 * Marvell Armada 370/XP SoC timer handling.
   3 *
   4 * Copyright (C) 2012 Marvell
   5 *
   6 * Lior Amsalem <alior@marvell.com>
   7 * Gregory CLEMENT <gregory.clement@free-electrons.com>
   8 * Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
   9 *
  10 * This file is licensed under the terms of the GNU General Public
  11 * License version 2.  This program is licensed "as is" without any
  12 * warranty of any kind, whether express or implied.
  13 *
  14 * Timer 0 is used as free-running clocksource, while timer 1 is
  15 * used as clock_event_device.
  16 */
  17
  18#include <linux/init.h>
  19#include <linux/platform_device.h>
  20#include <linux/kernel.h>
  21#include <linux/clk.h>
  22#include <linux/timer.h>
  23#include <linux/clockchips.h>
  24#include <linux/interrupt.h>
  25#include <linux/of.h>
  26#include <linux/of_irq.h>
  27#include <linux/of_address.h>
  28#include <linux/irq.h>
  29#include <linux/module.h>
  30
  31#include <asm/sched_clock.h>
  32#include <asm/localtimer.h>
  33#include <linux/percpu.h>
  34/*
  35 * Timer block registers.
  36 */
  37#define TIMER_CTRL_OFF          0x0000
  38#define  TIMER0_EN               0x0001
  39#define  TIMER0_RELOAD_EN        0x0002
  40#define  TIMER0_25MHZ            0x0800
  41#define  TIMER0_DIV(div)         ((div) << 19)
  42#define  TIMER1_EN               0x0004
  43#define  TIMER1_RELOAD_EN        0x0008
  44#define  TIMER1_25MHZ            0x1000
  45#define  TIMER1_DIV(div)         ((div) << 22)
  46#define TIMER_EVENTS_STATUS     0x0004
  47#define  TIMER0_CLR_MASK         (~0x1)
  48#define  TIMER1_CLR_MASK         (~0x100)
  49#define TIMER0_RELOAD_OFF       0x0010
  50#define TIMER0_VAL_OFF          0x0014
  51#define TIMER1_RELOAD_OFF       0x0018
  52#define TIMER1_VAL_OFF          0x001c
  53
  54#define LCL_TIMER_EVENTS_STATUS 0x0028
  55/* Global timers are connected to the coherency fabric clock, and the
  56   below divider reduces their incrementing frequency. */
  57#define TIMER_DIVIDER_SHIFT     5
  58#define TIMER_DIVIDER           (1 << TIMER_DIVIDER_SHIFT)
  59
  60/*
  61 * SoC-specific data.
  62 */
  63static void __iomem *timer_base, *local_base;
  64static unsigned int timer_clk;
  65static bool timer25Mhz = true;
  66
  67/*
  68 * Number of timer ticks per jiffy.
  69 */
  70static u32 ticks_per_jiffy;
  71
  72static struct clock_event_device __percpu **percpu_armada_370_xp_evt;
  73
  74static u32 notrace armada_370_xp_read_sched_clock(void)
  75{
  76        return ~readl(timer_base + TIMER0_VAL_OFF);
  77}
  78
  79/*
  80 * Clockevent handling.
  81 */
  82static int
  83armada_370_xp_clkevt_next_event(unsigned long delta,
  84                                struct clock_event_device *dev)
  85{
  86        u32 u;
  87        /*
  88         * Clear clockevent timer interrupt.
  89         */
  90        writel(TIMER0_CLR_MASK, local_base + LCL_TIMER_EVENTS_STATUS);
  91
  92        /*
  93         * Setup new clockevent timer value.
  94         */
  95        writel(delta, local_base + TIMER0_VAL_OFF);
  96
  97        /*
  98         * Enable the timer.
  99         */
 100        u = readl(local_base + TIMER_CTRL_OFF);
 101        u = ((u & ~TIMER0_RELOAD_EN) | TIMER0_EN |
 102             TIMER0_DIV(TIMER_DIVIDER_SHIFT));
 103        writel(u, local_base + TIMER_CTRL_OFF);
 104
 105        return 0;
 106}
 107
 108static void
 109armada_370_xp_clkevt_mode(enum clock_event_mode mode,
 110                          struct clock_event_device *dev)
 111{
 112        u32 u;
 113
 114        if (mode == CLOCK_EVT_MODE_PERIODIC) {
 115
 116                /*
 117                 * Setup timer to fire at 1/HZ intervals.
 118                 */
 119                writel(ticks_per_jiffy - 1, local_base + TIMER0_RELOAD_OFF);
 120                writel(ticks_per_jiffy - 1, local_base + TIMER0_VAL_OFF);
 121
 122                /*
 123                 * Enable timer.
 124                 */
 125
 126                u = readl(local_base + TIMER_CTRL_OFF);
 127
 128                writel((u | TIMER0_EN | TIMER0_RELOAD_EN |
 129                        TIMER0_DIV(TIMER_DIVIDER_SHIFT)),
 130                        local_base + TIMER_CTRL_OFF);
 131        } else {
 132                /*
 133                 * Disable timer.
 134                 */
 135                u = readl(local_base + TIMER_CTRL_OFF);
 136                writel(u & ~TIMER0_EN, local_base + TIMER_CTRL_OFF);
 137
 138                /*
 139                 * ACK pending timer interrupt.
 140                 */
 141                writel(TIMER0_CLR_MASK, local_base + LCL_TIMER_EVENTS_STATUS);
 142        }
 143}
 144
 145static struct clock_event_device armada_370_xp_clkevt = {
 146        .name           = "armada_370_xp_per_cpu_tick",
 147        .features       = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC,
 148        .shift          = 32,
 149        .rating         = 300,
 150        .set_next_event = armada_370_xp_clkevt_next_event,
 151        .set_mode       = armada_370_xp_clkevt_mode,
 152};
 153
 154static irqreturn_t armada_370_xp_timer_interrupt(int irq, void *dev_id)
 155{
 156        /*
 157         * ACK timer interrupt and call event handler.
 158         */
 159        struct clock_event_device *evt = *(struct clock_event_device **)dev_id;
 160
 161        writel(TIMER0_CLR_MASK, local_base + LCL_TIMER_EVENTS_STATUS);
 162        evt->event_handler(evt);
 163
 164        return IRQ_HANDLED;
 165}
 166
 167/*
 168 * Setup the local clock events for a CPU.
 169 */
 170static int __cpuinit armada_370_xp_timer_setup(struct clock_event_device *evt)
 171{
 172        u32 u;
 173        int cpu = smp_processor_id();
 174
 175        /* Use existing clock_event for cpu 0 */
 176        if (!smp_processor_id())
 177                return 0;
 178
 179        u = readl(local_base + TIMER_CTRL_OFF);
 180        if (timer25Mhz)
 181                writel(u | TIMER0_25MHZ, local_base + TIMER_CTRL_OFF);
 182        else
 183                writel(u & ~TIMER0_25MHZ, local_base + TIMER_CTRL_OFF);
 184
 185        evt->name               = armada_370_xp_clkevt.name;
 186        evt->irq                = armada_370_xp_clkevt.irq;
 187        evt->features           = armada_370_xp_clkevt.features;
 188        evt->shift              = armada_370_xp_clkevt.shift;
 189        evt->rating             = armada_370_xp_clkevt.rating,
 190        evt->set_next_event     = armada_370_xp_clkevt_next_event,
 191        evt->set_mode           = armada_370_xp_clkevt_mode,
 192        evt->cpumask            = cpumask_of(cpu);
 193
 194        *__this_cpu_ptr(percpu_armada_370_xp_evt) = evt;
 195
 196        clockevents_config_and_register(evt, timer_clk, 1, 0xfffffffe);
 197        enable_percpu_irq(evt->irq, 0);
 198
 199        return 0;
 200}
 201
 202static void  armada_370_xp_timer_stop(struct clock_event_device *evt)
 203{
 204        evt->set_mode(CLOCK_EVT_MODE_UNUSED, evt);
 205        disable_percpu_irq(evt->irq);
 206}
 207
 208static struct local_timer_ops armada_370_xp_local_timer_ops __cpuinitdata = {
 209        .setup  = armada_370_xp_timer_setup,
 210        .stop   =  armada_370_xp_timer_stop,
 211};
 212
 213void __init armada_370_xp_timer_init(void)
 214{
 215        u32 u;
 216        struct device_node *np;
 217        int res;
 218
 219        np = of_find_compatible_node(NULL, NULL, "marvell,armada-370-xp-timer");
 220        timer_base = of_iomap(np, 0);
 221        WARN_ON(!timer_base);
 222        local_base = of_iomap(np, 1);
 223
 224        if (of_find_property(np, "marvell,timer-25Mhz", NULL)) {
 225                /* The fixed 25MHz timer is available so let's use it */
 226                u = readl(local_base + TIMER_CTRL_OFF);
 227                writel(u | TIMER0_25MHZ,
 228                       local_base + TIMER_CTRL_OFF);
 229                u = readl(timer_base + TIMER_CTRL_OFF);
 230                writel(u | TIMER0_25MHZ,
 231                       timer_base + TIMER_CTRL_OFF);
 232                timer_clk = 25000000;
 233        } else {
 234                unsigned long rate = 0;
 235                struct clk *clk = of_clk_get(np, 0);
 236                WARN_ON(IS_ERR(clk));
 237                rate =  clk_get_rate(clk);
 238                u = readl(local_base + TIMER_CTRL_OFF);
 239                writel(u & ~(TIMER0_25MHZ),
 240                       local_base + TIMER_CTRL_OFF);
 241
 242                u = readl(timer_base + TIMER_CTRL_OFF);
 243                writel(u & ~(TIMER0_25MHZ),
 244                       timer_base + TIMER_CTRL_OFF);
 245
 246                timer_clk = rate / TIMER_DIVIDER;
 247                timer25Mhz = false;
 248        }
 249
 250        /*
 251         * We use timer 0 as clocksource, and private(local) timer 0
 252         * for clockevents
 253         */
 254        armada_370_xp_clkevt.irq = irq_of_parse_and_map(np, 4);
 255
 256        ticks_per_jiffy = (timer_clk + HZ / 2) / HZ;
 257
 258        /*
 259         * Set scale and timer for sched_clock.
 260         */
 261        setup_sched_clock(armada_370_xp_read_sched_clock, 32, timer_clk);
 262
 263        /*
 264         * Setup free-running clocksource timer (interrupts
 265         * disabled).
 266         */
 267        writel(0xffffffff, timer_base + TIMER0_VAL_OFF);
 268        writel(0xffffffff, timer_base + TIMER0_RELOAD_OFF);
 269
 270        u = readl(timer_base + TIMER_CTRL_OFF);
 271
 272        writel((u | TIMER0_EN | TIMER0_RELOAD_EN |
 273                TIMER0_DIV(TIMER_DIVIDER_SHIFT)), timer_base + TIMER_CTRL_OFF);
 274
 275        clocksource_mmio_init(timer_base + TIMER0_VAL_OFF,
 276                              "armada_370_xp_clocksource",
 277                              timer_clk, 300, 32, clocksource_mmio_readl_down);
 278
 279        /* Register the clockevent on the private timer of CPU 0 */
 280        armada_370_xp_clkevt.cpumask = cpumask_of(0);
 281        clockevents_config_and_register(&armada_370_xp_clkevt,
 282                                        timer_clk, 1, 0xfffffffe);
 283
 284        percpu_armada_370_xp_evt = alloc_percpu(struct clock_event_device *);
 285
 286
 287        /*
 288         * Setup clockevent timer (interrupt-driven).
 289         */
 290        *__this_cpu_ptr(percpu_armada_370_xp_evt) = &armada_370_xp_clkevt;
 291        res = request_percpu_irq(armada_370_xp_clkevt.irq,
 292                                armada_370_xp_timer_interrupt,
 293                                armada_370_xp_clkevt.name,
 294                                percpu_armada_370_xp_evt);
 295        if (!res) {
 296                enable_percpu_irq(armada_370_xp_clkevt.irq, 0);
 297#ifdef CONFIG_LOCAL_TIMERS
 298                local_timer_register(&armada_370_xp_local_timer_ops);
 299#endif
 300        }
 301}
 302
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.