timer-tmu.c 4.2 KB
Newer Older
P
Paul Mundt 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
/*
 * arch/sh/kernel/timers/timer-tmu.c - TMU Timer Support
 *
 *  Copyright (C) 2005  Paul Mundt
 *
 * TMU handling code hacked out of arch/sh/kernel/time.c
 *
 *  Copyright (C) 1999  Tetsuya Okada & Niibe Yutaka
 *  Copyright (C) 2000  Philipp Rumpf <prumpf@tux.org>
 *  Copyright (C) 2002, 2003, 2004  Paul Mundt
 *  Copyright (C) 2002  M. R. Brown  <mrbrown@linux-sh.org>
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/seqlock.h>
#include <asm/timer.h>
#include <asm/rtc.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/clock.h>

#define TMU_TOCR_INIT	0x00
#define TMU0_TCR_INIT	0x0020
#define TMU_TSTR_INIT	1

#define TMU0_TCR_CALIB	0x0000

static DEFINE_SPINLOCK(tmu0_lock);

static unsigned long tmu_timer_get_offset(void)
{
	int count;
	unsigned long flags;

	static int count_p = 0x7fffffff;    /* for the first call after boot */
	static unsigned long jiffies_p = 0;

	/*
	 * cache volatile jiffies temporarily; we have IRQs turned off.
	 */
	unsigned long jiffies_t;

	spin_lock_irqsave(&tmu0_lock, flags);
	/* timer count may underflow right here */
	count = ctrl_inl(TMU0_TCNT);	/* read the latched count */

	jiffies_t = jiffies;

	/*
	 * avoiding timer inconsistencies (they are rare, but they happen)...
	 * there is one kind of problem that must be avoided here:
	 *  1. the timer counter underflows
	 */

	if (jiffies_t == jiffies_p) {
		if (count > count_p) {
			/* the nutcase */
			if (ctrl_inw(TMU0_TCR) & 0x100) { /* Check UNF bit */
				count -= LATCH;
			} else {
				printk("%s (): hardware timer problem?\n",
				       __FUNCTION__);
			}
		}
	} else
		jiffies_p = jiffies_t;

	count_p = count;
	spin_unlock_irqrestore(&tmu0_lock, flags);

	count = ((LATCH-1) - count) * TICK_SIZE;
	count = (count + LATCH/2) / LATCH;

	return count;
}

static irqreturn_t tmu_timer_interrupt(int irq, void *dev_id,
				       struct pt_regs *regs)
{
	unsigned long timer_status;

	/* Clear UNF bit */
	timer_status = ctrl_inw(TMU0_TCR);
	timer_status &= ~0x100;
	ctrl_outw(timer_status, TMU0_TCR);

	/*
	 * Here we are in the timer irq handler. We just have irqs locally
	 * disabled but we don't know if the timer_bh is running on the other
	 * CPU. We need to avoid to SMP race with it. NOTE: we don' t need
	 * the irq version of write_lock because as just said we have irq
	 * locally disabled. -arca
	 */
	write_seqlock(&xtime_lock);
	handle_timer_tick(regs);
	write_sequnlock(&xtime_lock);

	return IRQ_HANDLED;
}

static struct irqaction tmu_irq = {
	.name		= "timer",
	.handler	= tmu_timer_interrupt,
110
	.flags		= IRQF_DISABLED,
P
Paul Mundt 已提交
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
	.mask		= CPU_MASK_NONE,
};

static void tmu_clk_init(struct clk *clk)
{
	u8 divisor = TMU0_TCR_INIT & 0x7;
	ctrl_outw(TMU0_TCR_INIT, TMU0_TCR);
	clk->rate = clk->parent->rate / (4 << (divisor << 1));
}

static void tmu_clk_recalc(struct clk *clk)
{
	u8 divisor = ctrl_inw(TMU0_TCR) & 0x7;
	clk->rate = clk->parent->rate / (4 << (divisor << 1));
}

static struct clk_ops tmu_clk_ops = {
	.init		= tmu_clk_init,
	.recalc		= tmu_clk_recalc,
};

static struct clk tmu0_clk = {
	.name		= "tmu0_clk",
	.ops		= &tmu_clk_ops,
};

A
Andriy Skulysh 已提交
137 138 139 140 141 142 143 144 145 146 147 148
static int tmu_timer_start(void)
{
	ctrl_outb(TMU_TSTR_INIT, TMU_TSTR);
	return 0;
}

static int tmu_timer_stop(void)
{
	ctrl_outb(0, TMU_TSTR);
	return 0;
}

P
Paul Mundt 已提交
149 150 151 152 153 154 155 156 157
static int tmu_timer_init(void)
{
	unsigned long interval;

	setup_irq(TIMER_IRQ, &tmu_irq);

	tmu0_clk.parent = clk_get("module_clk");

	/* Start TMU0 */
A
Andriy Skulysh 已提交
158
	tmu_timer_stop();
P
Paul Mundt 已提交
159 160 161 162 163 164 165 166 167 168 169 170 171
#if !defined(CONFIG_CPU_SUBTYPE_SH7300) && !defined(CONFIG_CPU_SUBTYPE_SH7760)
	ctrl_outb(TMU_TOCR_INIT, TMU_TOCR);
#endif

	clk_register(&tmu0_clk);
	clk_enable(&tmu0_clk);

	interval = (clk_get_rate(&tmu0_clk) + HZ / 2) / HZ;
	printk(KERN_INFO "Interval = %ld\n", interval);

	ctrl_outl(interval, TMU0_TCOR);
	ctrl_outl(interval, TMU0_TCNT);

A
Andriy Skulysh 已提交
172
	tmu_timer_start();
P
Paul Mundt 已提交
173 174 175 176 177 178

	return 0;
}

struct sys_timer_ops tmu_timer_ops = {
	.init		= tmu_timer_init,
A
Andriy Skulysh 已提交
179 180
	.start		= tmu_timer_start,
	.stop		= tmu_timer_stop,
181
#ifndef CONFIG_GENERIC_TIME
P
Paul Mundt 已提交
182
	.get_offset	= tmu_timer_get_offset,
183
#endif
P
Paul Mundt 已提交
184 185 186 187 188 189
};

struct sys_timer tmu_timer = {
	.name	= "tmu",
	.ops	= &tmu_timer_ops,
};