Logo Search packages:      
Sourcecode: linux version File versions  Download package

time.c

/*
 * linux/arch/arm/mach-mmp/time.c
 *
 *   Support for clocksource and clockevents
 *
 * Copyright (C) 2008 Marvell International Ltd.
 * All rights reserved.
 *
 *   2008-04-11: Jason Chagas <Jason.chagas@marvell.com>
 *   2008-10-08: Bin Yang <bin.yang@marvell.com>
 *
 * The timers module actually includes three timers, each timer with upto
 * three match comparators. Timer #0 is used here in free-running mode as
 * the clock source, and match comparator #1 used as clock event device.
 *
 * 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/kernel.h>
#include <linux/interrupt.h>
#include <linux/clockchips.h>

#include <linux/io.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/cnt32_to_63.h>

#include <mach/addr-map.h>
#include <mach/regs-timers.h>
#include <mach/irqs.h>

#include "clock.h"

#define TIMERS_VIRT_BASE      TIMERS1_VIRT_BASE

#define MAX_DELTA       (0xfffffffe)
#define MIN_DELTA       (16)

#define TCR2NS_SCALE_FACTOR   10

static unsigned long tcr2ns_scale;

static void __init set_tcr2ns_scale(unsigned long tcr_rate)
{
      unsigned long long v = 1000000000ULL << TCR2NS_SCALE_FACTOR;
      do_div(v, tcr_rate);
      tcr2ns_scale = v;
      /*
       * We want an even value to automatically clear the top bit
       * returned by cnt32_to_63() without an additional run time
       * instruction. So if the LSB is 1 then round it up.
       */
      if (tcr2ns_scale & 1)
            tcr2ns_scale++;
}

/*
 * FIXME: the timer needs some delay to stablize the counter capture
 */
static inline uint32_t timer_read(void)
{
      int delay = 100;

      __raw_writel(1, TIMERS_VIRT_BASE + TMR_CVWR(0));

      while (delay--)
            cpu_relax();

      return __raw_readl(TIMERS_VIRT_BASE + TMR_CVWR(0));
}

unsigned long long sched_clock(void)
{
      unsigned long long v = cnt32_to_63(timer_read());
      return (v * tcr2ns_scale) >> TCR2NS_SCALE_FACTOR;
}

static irqreturn_t timer_interrupt(int irq, void *dev_id)
{
      struct clock_event_device *c = dev_id;

      /* disable and clear pending interrupt status */
      __raw_writel(0x0, TIMERS_VIRT_BASE + TMR_IER(0));
      __raw_writel(0x1, TIMERS_VIRT_BASE + TMR_ICR(0));
      c->event_handler(c);
      return IRQ_HANDLED;
}

static int timer_set_next_event(unsigned long delta,
                        struct clock_event_device *dev)
{
      unsigned long flags, next;

      local_irq_save(flags);

      /* clear pending interrupt status and enable */
      __raw_writel(0x01, TIMERS_VIRT_BASE + TMR_ICR(0));
      __raw_writel(0x01, TIMERS_VIRT_BASE + TMR_IER(0));

      next = timer_read() + delta;
      __raw_writel(next, TIMERS_VIRT_BASE + TMR_TN_MM(0, 0));

      local_irq_restore(flags);
      return 0;
}

static void timer_set_mode(enum clock_event_mode mode,
                     struct clock_event_device *dev)
{
      unsigned long flags;

      local_irq_save(flags);
      switch (mode) {
      case CLOCK_EVT_MODE_ONESHOT:
      case CLOCK_EVT_MODE_UNUSED:
      case CLOCK_EVT_MODE_SHUTDOWN:
            /* disable the matching interrupt */
            __raw_writel(0x00, TIMERS_VIRT_BASE + TMR_IER(0));
            break;
      case CLOCK_EVT_MODE_RESUME:
      case CLOCK_EVT_MODE_PERIODIC:
            break;
      }
      local_irq_restore(flags);
}

static struct clock_event_device ckevt = {
      .name       = "clockevent",
      .features   = CLOCK_EVT_FEAT_ONESHOT,
      .shift            = 32,
      .rating           = 200,
      .set_next_event   = timer_set_next_event,
      .set_mode   = timer_set_mode,
};

static cycle_t clksrc_read(struct clocksource *cs)
{
      return timer_read();
}

static struct clocksource cksrc = {
      .name       = "clocksource",
      .shift            = 20,
      .rating           = 200,
      .read       = clksrc_read,
      .mask       = CLOCKSOURCE_MASK(32),
      .flags            = CLOCK_SOURCE_IS_CONTINUOUS,
};

static void __init timer_config(void)
{
      uint32_t ccr = __raw_readl(TIMERS_VIRT_BASE + TMR_CCR);
      uint32_t cer = __raw_readl(TIMERS_VIRT_BASE + TMR_CER);
      uint32_t cmr = __raw_readl(TIMERS_VIRT_BASE + TMR_CMR);

      __raw_writel(cer & ~0x1, TIMERS_VIRT_BASE + TMR_CER); /* disable */

      ccr &= TMR_CCR_CS_0(0x3);
      __raw_writel(ccr, TIMERS_VIRT_BASE + TMR_CCR);

      /* free-running mode */
      __raw_writel(cmr | 0x01, TIMERS_VIRT_BASE + TMR_CMR);

      __raw_writel(0x0, TIMERS_VIRT_BASE + TMR_PLCR(0)); /* free-running */
      __raw_writel(0x7, TIMERS_VIRT_BASE + TMR_ICR(0));  /* clear status */
      __raw_writel(0x0, TIMERS_VIRT_BASE + TMR_IER(0));

      /* enable timer counter */
      __raw_writel(cer | 0x01, TIMERS_VIRT_BASE + TMR_CER);
}

static struct irqaction timer_irq = {
      .name       = "timer",
      .flags            = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
      .handler    = timer_interrupt,
      .dev_id           = &ckevt,
};

void __init timer_init(int irq)
{
      timer_config();

      set_tcr2ns_scale(CLOCK_TICK_RATE);

      ckevt.mult = div_sc(CLOCK_TICK_RATE, NSEC_PER_SEC, ckevt.shift);
      ckevt.max_delta_ns = clockevent_delta2ns(MAX_DELTA, &ckevt);
      ckevt.min_delta_ns = clockevent_delta2ns(MIN_DELTA, &ckevt);
      ckevt.cpumask = cpumask_of(0);

      cksrc.mult = clocksource_hz2mult(CLOCK_TICK_RATE, cksrc.shift);

      setup_irq(irq, &timer_irq);

      clocksource_register(&cksrc);
      clockevents_register_device(&ckevt);
}

Generated by  Doxygen 1.6.0   Back to index