2007-03-13 01:15:16 +08:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2004-2007 Atmel Corporation
|
|
|
|
*
|
|
|
|
* Based on MIPS implementation arch/mips/kernel/time.c
|
|
|
|
* Copyright 2001 MontaVista Software Inc.
|
|
|
|
*
|
|
|
|
* 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/clk.h>
|
|
|
|
#include <linux/clocksource.h>
|
|
|
|
#include <linux/time.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/irq.h>
|
|
|
|
#include <linux/kernel_stat.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/profile.h>
|
|
|
|
#include <linux/sysdev.h>
|
|
|
|
#include <linux/err.h>
|
|
|
|
|
|
|
|
#include <asm/div64.h>
|
|
|
|
#include <asm/sysreg.h>
|
|
|
|
#include <asm/io.h>
|
|
|
|
#include <asm/sections.h>
|
|
|
|
|
|
|
|
#include <asm/arch/time.h>
|
|
|
|
|
|
|
|
/* how many counter cycles in a jiffy? */
|
|
|
|
static u32 cycles_per_jiffy;
|
|
|
|
|
|
|
|
/* the count value for the next timer interrupt */
|
|
|
|
static u32 expirelo;
|
|
|
|
|
|
|
|
/* the I/O registers of the TC module */
|
|
|
|
static void __iomem *ioregs;
|
|
|
|
|
|
|
|
cycle_t read_cycle_count(void)
|
|
|
|
{
|
|
|
|
return (cycle_t)timer_read(ioregs, 0, CV);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct clocksource clocksource_avr32 = {
|
|
|
|
.name = "avr32",
|
|
|
|
.rating = 342,
|
|
|
|
.read = read_cycle_count,
|
|
|
|
.mask = CLOCKSOURCE_MASK(16),
|
|
|
|
.shift = 16,
|
|
|
|
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void avr32_timer_ack(void)
|
|
|
|
{
|
|
|
|
u16 count = expirelo;
|
|
|
|
|
|
|
|
/* Ack this timer interrupt and set the next one, use a u16
|
|
|
|
* variable so it will wrap around correctly */
|
|
|
|
count += cycles_per_jiffy;
|
|
|
|
expirelo = count;
|
|
|
|
timer_write(ioregs, 0, RC, expirelo);
|
|
|
|
|
|
|
|
/* Check to see if we have missed any timer interrupts */
|
|
|
|
count = timer_read(ioregs, 0, CV);
|
|
|
|
if ((count - expirelo) < 0x7fff) {
|
|
|
|
expirelo = count + cycles_per_jiffy;
|
|
|
|
timer_write(ioregs, 0, RC, expirelo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 avr32_hpt_read(void)
|
|
|
|
{
|
|
|
|
return timer_read(ioregs, 0, CV);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int avr32_timer_calc_div_and_set_jiffies(struct clk *pclk)
|
|
|
|
{
|
|
|
|
unsigned int cycles_max = (clocksource_avr32.mask + 1) / 2;
|
|
|
|
unsigned int divs[] = { 4, 8, 16, 32 };
|
2007-10-23 03:18:24 +08:00
|
|
|
int divs_size = ARRAY_SIZE(divs);
|
2007-03-13 01:15:16 +08:00
|
|
|
int i = 0;
|
|
|
|
unsigned long count_hz;
|
|
|
|
unsigned long shift;
|
|
|
|
unsigned long mult;
|
|
|
|
int clock_div = -1;
|
|
|
|
u64 tmp;
|
|
|
|
|
|
|
|
shift = clocksource_avr32.shift;
|
|
|
|
|
|
|
|
do {
|
|
|
|
count_hz = clk_get_rate(pclk) / divs[i];
|
|
|
|
mult = clocksource_hz2mult(count_hz, shift);
|
|
|
|
clocksource_avr32.mult = mult;
|
|
|
|
|
|
|
|
tmp = TICK_NSEC;
|
|
|
|
tmp <<= shift;
|
|
|
|
tmp += mult / 2;
|
|
|
|
do_div(tmp, mult);
|
|
|
|
|
|
|
|
cycles_per_jiffy = tmp;
|
|
|
|
} while (cycles_per_jiffy > cycles_max && ++i < divs_size);
|
|
|
|
|
|
|
|
clock_div = i + 1;
|
|
|
|
|
|
|
|
if (clock_div > divs_size) {
|
|
|
|
pr_debug("timer: could not calculate clock divider\n");
|
|
|
|
return -EFAULT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Set the clock divider */
|
|
|
|
timer_write(ioregs, 0, CMR, TIMER_BF(CMR_TCCLKS, clock_div));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int avr32_hpt_init(unsigned int count)
|
|
|
|
{
|
|
|
|
struct resource *regs;
|
|
|
|
struct clk *pclk;
|
|
|
|
int irq = -1;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
ret = -ENXIO;
|
|
|
|
|
|
|
|
irq = platform_get_irq(&at32_systc0_device, 0);
|
|
|
|
if (irq < 0) {
|
|
|
|
pr_debug("timer: could not get irq\n");
|
|
|
|
goto out_error;
|
|
|
|
}
|
|
|
|
|
|
|
|
pclk = clk_get(&at32_systc0_device.dev, "pclk");
|
|
|
|
if (IS_ERR(pclk)) {
|
|
|
|
pr_debug("timer: could not get clk: %ld\n", PTR_ERR(pclk));
|
|
|
|
goto out_error;
|
|
|
|
}
|
2007-03-14 20:23:44 +08:00
|
|
|
clk_enable(pclk);
|
2007-03-13 01:15:16 +08:00
|
|
|
|
|
|
|
regs = platform_get_resource(&at32_systc0_device, IORESOURCE_MEM, 0);
|
|
|
|
if (!regs) {
|
|
|
|
pr_debug("timer: could not get resource\n");
|
|
|
|
goto out_error_clk;
|
|
|
|
}
|
|
|
|
|
|
|
|
ioregs = ioremap(regs->start, regs->end - regs->start + 1);
|
|
|
|
if (!ioregs) {
|
|
|
|
pr_debug("timer: could not get ioregs\n");
|
|
|
|
goto out_error_clk;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = avr32_timer_calc_div_and_set_jiffies(pclk);
|
|
|
|
if (ret)
|
|
|
|
goto out_error_io;
|
|
|
|
|
|
|
|
ret = setup_irq(irq, &timer_irqaction);
|
|
|
|
if (ret) {
|
|
|
|
pr_debug("timer: could not request irq %d: %d\n",
|
|
|
|
irq, ret);
|
|
|
|
goto out_error_io;
|
|
|
|
}
|
|
|
|
|
|
|
|
expirelo = (timer_read(ioregs, 0, CV) / cycles_per_jiffy + 1)
|
|
|
|
* cycles_per_jiffy;
|
|
|
|
|
|
|
|
/* Enable clock and interrupts on RC compare */
|
|
|
|
timer_write(ioregs, 0, CCR, TIMER_BIT(CCR_CLKEN));
|
|
|
|
timer_write(ioregs, 0, IER, TIMER_BIT(IER_CPCS));
|
|
|
|
/* Set cycles to first interrupt */
|
|
|
|
timer_write(ioregs, 0, RC, expirelo);
|
|
|
|
|
|
|
|
printk(KERN_INFO "timer: AT32AP system timer/counter at 0x%p irq %d\n",
|
|
|
|
ioregs, irq);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_error_io:
|
|
|
|
iounmap(ioregs);
|
|
|
|
out_error_clk:
|
|
|
|
clk_put(pclk);
|
|
|
|
out_error:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int avr32_hpt_start(void)
|
|
|
|
{
|
|
|
|
timer_write(ioregs, 0, CCR, TIMER_BIT(CCR_SWTRG));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
irqreturn_t timer_interrupt(int irq, void *dev_id)
|
|
|
|
{
|
|
|
|
unsigned int sr = timer_read(ioregs, 0, SR);
|
|
|
|
|
|
|
|
if (sr & TIMER_BIT(SR_CPCS)) {
|
|
|
|
/* ack timer interrupt and try to set next interrupt */
|
|
|
|
avr32_timer_ack();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Call the generic timer interrupt handler
|
|
|
|
*/
|
|
|
|
write_seqlock(&xtime_lock);
|
|
|
|
do_timer(1);
|
|
|
|
write_sequnlock(&xtime_lock);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In UP mode, we call local_timer_interrupt() to do profiling
|
|
|
|
* and process accounting.
|
|
|
|
*
|
|
|
|
* SMP is not supported yet.
|
|
|
|
*/
|
|
|
|
local_timer_interrupt(irq, dev_id);
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
return IRQ_NONE;
|
|
|
|
}
|