linux/arch/arm/plat-nomadik/timer.c
<<
>>
Prefs
   1/*
   2 *  linux/arch/arm/plat-nomadik/timer.c
   3 *
   4 * Copyright (C) 2008 STMicroelectronics
   5 * Copyright (C) 2010 Alessandro Rubini
   6 * Copyright (C) 2010 Linus Walleij for ST-Ericsson
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License version 2, as
  10 * published by the Free Software Foundation.
  11 */
  12#include <linux/init.h>
  13#include <linux/interrupt.h>
  14#include <linux/irq.h>
  15#include <linux/io.h>
  16#include <linux/clockchips.h>
  17#include <linux/clk.h>
  18#include <linux/jiffies.h>
  19#include <linux/err.h>
  20#include <linux/sched.h>
  21#include <asm/mach/time.h>
  22#include <asm/sched_clock.h>
  23
  24#include <plat/mtu.h>
  25
  26void __iomem *mtu_base; /* Assigned by machine code */
  27
  28/*
  29 * Kernel assumes that sched_clock can be called early
  30 * but the MTU may not yet be initialized.
  31 */
  32static cycle_t nmdk_read_timer_dummy(struct clocksource *cs)
  33{
  34        return 0;
  35}
  36
  37/* clocksource: MTU decrements, so we negate the value being read. */
  38static cycle_t nmdk_read_timer(struct clocksource *cs)
  39{
  40        return -readl(mtu_base + MTU_VAL(0));
  41}
  42
  43static struct clocksource nmdk_clksrc = {
  44        .name           = "mtu_0",
  45        .rating         = 200,
  46        .read           = nmdk_read_timer_dummy,
  47        .mask           = CLOCKSOURCE_MASK(32),
  48        .flags          = CLOCK_SOURCE_IS_CONTINUOUS,
  49};
  50
  51/*
  52 * Override the global weak sched_clock symbol with this
  53 * local implementation which uses the clocksource to get some
  54 * better resolution when scheduling the kernel.
  55 */
  56static DEFINE_CLOCK_DATA(cd);
  57
  58unsigned long long notrace sched_clock(void)
  59{
  60        u32 cyc;
  61
  62        if (unlikely(!mtu_base))
  63                return 0;
  64
  65        cyc = -readl(mtu_base + MTU_VAL(0));
  66        return cyc_to_sched_clock(&cd, cyc, (u32)~0);
  67}
  68
  69static void notrace nomadik_update_sched_clock(void)
  70{
  71        u32 cyc = -readl(mtu_base + MTU_VAL(0));
  72        update_sched_clock(&cd, cyc, (u32)~0);
  73}
  74
  75/* Clockevent device: use one-shot mode */
  76static void nmdk_clkevt_mode(enum clock_event_mode mode,
  77                             struct clock_event_device *dev)
  78{
  79        u32 cr;
  80
  81        switch (mode) {
  82        case CLOCK_EVT_MODE_PERIODIC:
  83                pr_err("%s: periodic mode not supported\n", __func__);
  84                break;
  85        case CLOCK_EVT_MODE_ONESHOT:
  86                /* Load highest value, enable device, enable interrupts */
  87                cr = readl(mtu_base + MTU_CR(1));
  88                writel(0, mtu_base + MTU_LR(1));
  89                writel(cr | MTU_CRn_ENA, mtu_base + MTU_CR(1));
  90                writel(1 << 1, mtu_base + MTU_IMSC);
  91                break;
  92        case CLOCK_EVT_MODE_SHUTDOWN:
  93        case CLOCK_EVT_MODE_UNUSED:
  94                /* disable irq */
  95                writel(0, mtu_base + MTU_IMSC);
  96                /* disable timer */
  97                cr = readl(mtu_base + MTU_CR(1));
  98                cr &= ~MTU_CRn_ENA;
  99                writel(cr, mtu_base + MTU_CR(1));
 100                /* load some high default value */
 101                writel(0xffffffff, mtu_base + MTU_LR(1));
 102                break;
 103        case CLOCK_EVT_MODE_RESUME:
 104                break;
 105        }
 106}
 107
 108static int nmdk_clkevt_next(unsigned long evt, struct clock_event_device *ev)
 109{
 110        /* writing the value has immediate effect */
 111        writel(evt, mtu_base + MTU_LR(1));
 112        return 0;
 113}
 114
 115static struct clock_event_device nmdk_clkevt = {
 116        .name           = "mtu_1",
 117        .features       = CLOCK_EVT_FEAT_ONESHOT,
 118        .rating         = 200,
 119        .set_mode       = nmdk_clkevt_mode,
 120        .set_next_event = nmdk_clkevt_next,
 121};
 122
 123/*
 124 * IRQ Handler for timer 1 of the MTU block.
 125 */
 126static irqreturn_t nmdk_timer_interrupt(int irq, void *dev_id)
 127{
 128        struct clock_event_device *evdev = dev_id;
 129
 130        writel(1 << 1, mtu_base + MTU_ICR); /* Interrupt clear reg */
 131        evdev->event_handler(evdev);
 132        return IRQ_HANDLED;
 133}
 134
 135static struct irqaction nmdk_timer_irq = {
 136        .name           = "Nomadik Timer Tick",
 137        .flags          = IRQF_DISABLED | IRQF_TIMER,
 138        .handler        = nmdk_timer_interrupt,
 139        .dev_id         = &nmdk_clkevt,
 140};
 141
 142void __init nmdk_timer_init(void)
 143{
 144        unsigned long rate;
 145        struct clk *clk0;
 146        u32 cr = MTU_CRn_32BITS;
 147
 148        clk0 = clk_get_sys("mtu0", NULL);
 149        BUG_ON(IS_ERR(clk0));
 150
 151        clk_enable(clk0);
 152
 153        /*
 154         * Tick rate is 2.4MHz for Nomadik and 2.4Mhz, 100MHz or 133 MHz
 155         * for ux500.
 156         * Use a divide-by-16 counter if the tick rate is more than 32MHz.
 157         * At 32 MHz, the timer (with 32 bit counter) can be programmed
 158         * to wake-up at a max 127s a head in time. Dividing a 2.4 MHz timer
 159         * with 16 gives too low timer resolution.
 160         */
 161        rate = clk_get_rate(clk0);
 162        if (rate > 32000000) {
 163                rate /= 16;
 164                cr |= MTU_CRn_PRESCALE_16;
 165        } else {
 166                cr |= MTU_CRn_PRESCALE_1;
 167        }
 168
 169        /* Timer 0 is the free running clocksource */
 170        writel(cr, mtu_base + MTU_CR(0));
 171        writel(0, mtu_base + MTU_LR(0));
 172        writel(0, mtu_base + MTU_BGLR(0));
 173        writel(cr | MTU_CRn_ENA, mtu_base + MTU_CR(0));
 174
 175        /* Now the clock source is ready */
 176        nmdk_clksrc.read = nmdk_read_timer;
 177
 178        if (clocksource_register_hz(&nmdk_clksrc, rate))
 179                pr_err("timer: failed to initialize clock source %s\n",
 180                       nmdk_clksrc.name);
 181
 182        init_sched_clock(&cd, nomadik_update_sched_clock, 32, rate);
 183
 184        /* Timer 1 is used for events */
 185
 186        clockevents_calc_mult_shift(&nmdk_clkevt, rate, MTU_MIN_RANGE);
 187
 188        writel(cr | MTU_CRn_ONESHOT, mtu_base + MTU_CR(1)); /* off, currently */
 189
 190        nmdk_clkevt.max_delta_ns =
 191                clockevent_delta2ns(0xffffffff, &nmdk_clkevt);
 192        nmdk_clkevt.min_delta_ns =
 193                clockevent_delta2ns(0x00000002, &nmdk_clkevt);
 194        nmdk_clkevt.cpumask     = cpumask_of(0);
 195
 196        /* Register irq and clockevents */
 197        setup_irq(IRQ_MTU0, &nmdk_timer_irq);
 198        clockevents_register_device(&nmdk_clkevt);
 199}
 200