localtimer.c 4.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 *  linux/arch/arm/mach-realview/localtimer.c
 *
 *  Copyright (C) 2002 ARM Ltd.
 *  All Rights Reserved
 *
 * 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/delay.h>
#include <linux/device.h>
#include <linux/smp.h>
16
#include <linux/jiffies.h>
17 18
#include <linux/percpu.h>
#include <linux/clockchips.h>
19
#include <linux/irq.h>
20
#include <linux/io.h>
21 22 23

#include <asm/hardware/arm_twd.h>
#include <asm/hardware/gic.h>
24
#include <mach/hardware.h>
25 26
#include <asm/irq.h>

27 28 29 30 31 32 33 34 35 36 37 38 39 40
static DEFINE_PER_CPU(struct clock_event_device, local_clockevent);

/*
 * Used on SMP for either the local timer or IPI_TIMER
 */
void local_timer_interrupt(void)
{
	struct clock_event_device *clk = &__get_cpu_var(local_clockevent);

	clk->event_handler(clk);
}

#ifdef CONFIG_LOCAL_TIMERS

41
/* set up by the platform code */
42
void __iomem *twd_base;
43

44 45
static unsigned long mpcore_timer_rate;

46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
static void local_timer_set_mode(enum clock_event_mode mode,
				 struct clock_event_device *clk)
{
	unsigned long ctrl;

	switch(mode) {
	case CLOCK_EVT_MODE_PERIODIC:
		/* timer load already set up */
		ctrl = TWD_TIMER_CONTROL_ENABLE | TWD_TIMER_CONTROL_IT_ENABLE
			| TWD_TIMER_CONTROL_PERIODIC;
		break;
	case CLOCK_EVT_MODE_ONESHOT:
		/* period set, and timer enabled in 'next_event' hook */
		ctrl = TWD_TIMER_CONTROL_IT_ENABLE | TWD_TIMER_CONTROL_ONESHOT;
		break;
	case CLOCK_EVT_MODE_UNUSED:
	case CLOCK_EVT_MODE_SHUTDOWN:
	default:
		ctrl = 0;
	}

67
	__raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL);
68 69 70 71 72
}

static int local_timer_set_next_event(unsigned long evt,
				      struct clock_event_device *unused)
{
73
	unsigned long ctrl = __raw_readl(twd_base + TWD_TIMER_CONTROL);
74

75 76
	__raw_writel(evt, twd_base + TWD_TIMER_COUNTER);
	__raw_writel(ctrl | TWD_TIMER_CONTROL_ENABLE, twd_base + TWD_TIMER_CONTROL);
77 78 79 80

	return 0;
}

81 82 83
/*
 * local_timer_ack: checks for a local timer interrupt.
 *
S
Simon Arlott 已提交
84
 * If a local timer interrupt has occurred, acknowledge and return 1.
85 86 87 88
 * Otherwise, return 0.
 */
int local_timer_ack(void)
{
89 90
	if (__raw_readl(twd_base + TWD_TIMER_INTSTAT)) {
		__raw_writel(1, twd_base + TWD_TIMER_INTSTAT);
91 92 93 94 95 96
		return 1;
	}

	return 0;
}

97
static void __cpuinit twd_calibrate_rate(void)
98
{
99
	unsigned long load, count;
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
	u64 waitjiffies;

	/*
	 * If this is the first time round, we need to work out how fast
	 * the timer ticks
	 */
	if (mpcore_timer_rate == 0) {
		printk("Calibrating local timer... ");

		/* Wait for a tick to start */
		waitjiffies = get_jiffies_64() + 1;

		while (get_jiffies_64() < waitjiffies)
			udelay(10);

		/* OK, now the tick has started, let's get the timer going */
		waitjiffies += 5;

				 /* enable, no interrupt or reload */
119
		__raw_writel(0x1, twd_base + TWD_TIMER_CONTROL);
120 121

				 /* maximum value */
122
		__raw_writel(0xFFFFFFFFU, twd_base + TWD_TIMER_COUNTER);
123 124 125 126

		while (get_jiffies_64() < waitjiffies)
			udelay(10);

127
		count = __raw_readl(twd_base + TWD_TIMER_COUNTER);
128 129 130 131 132 133 134 135 136

		mpcore_timer_rate = (0xFFFFFFFFU - count) * (HZ / 5);

		printk("%lu.%02luMHz.\n", mpcore_timer_rate / 1000000,
			(mpcore_timer_rate / 100000) % 100);
	}

	load = mpcore_timer_rate / HZ;

137
	__raw_writel(load, twd_base + TWD_TIMER_LOAD);
138
}
139

140 141 142
/*
 * Setup the local clock events for a CPU.
 */
143
void __cpuinit local_timer_setup(void)
144
{
145
	unsigned int cpu = smp_processor_id();
146 147
	struct clock_event_device *clk = &per_cpu(local_clockevent, cpu);
	unsigned long flags;
148

149
	twd_calibrate_rate();
150

151 152 153 154 155 156
	clk->name		= "local_timer";
	clk->features		= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
	clk->rating		= 350;
	clk->set_mode		= local_timer_set_mode;
	clk->set_next_event	= local_timer_set_next_event;
	clk->irq		= IRQ_LOCALTIMER;
157
	clk->cpumask		= cpumask_of(cpu);
158 159 160 161
	clk->shift		= 20;
	clk->mult		= div_sc(mpcore_timer_rate, NSEC_PER_SEC, clk->shift);
	clk->max_delta_ns	= clockevent_delta2ns(0xffffffff, clk);
	clk->min_delta_ns	= clockevent_delta2ns(0xf, clk);
162 163

	/* Make sure our local interrupt controller has this enabled */
164 165 166 167 168
	local_irq_save(flags);
	get_irq_chip(IRQ_LOCALTIMER)->unmask(IRQ_LOCALTIMER);
	local_irq_restore(flags);

	clockevents_register_device(clk);
169 170 171 172 173
}

/*
 * take a local timer down
 */
174
void __cpuexit local_timer_stop(void)
175
{
176
	__raw_writel(0, twd_base + TWD_TIMER_CONTROL);
177
}
178 179 180 181 182 183 184 185

#else	/* CONFIG_LOCAL_TIMERS */

static void dummy_timer_set_mode(enum clock_event_mode mode,
				 struct clock_event_device *clk)
{
}

186
void __cpuinit local_timer_setup(void)
187
{
188
	unsigned int cpu = smp_processor_id();
189 190 191
	struct clock_event_device *clk = &per_cpu(local_clockevent, cpu);

	clk->name		= "dummy_timer";
192 193 194 195
	clk->features		= CLOCK_EVT_FEAT_ONESHOT |
				  CLOCK_EVT_FEAT_PERIODIC |
				  CLOCK_EVT_FEAT_DUMMY;
	clk->rating		= 400;
196
	clk->mult               = 1;
197 198
	clk->set_mode		= dummy_timer_set_mode;
	clk->broadcast		= smp_timer_broadcast;
199
	clk->cpumask		= cpumask_of(cpu);
200 201 202 203 204

	clockevents_register_device(clk);
}

#endif	/* !CONFIG_LOCAL_TIMERS */