ARM: footbridge: convert to clockevents/clocksource

The Footbridge platforms have some reasonable timers in the host bridge,
which we use for most footbridge-based platforms.  However, NetWinder's
clock these using a spread-spectrum clock which makes them too unstable
for time keeping.  So we have to rely on the PIT.

Convert both Footbridge timers and PIT timers to use the clocksource
and clockevent infrastructure.  Tested on Netwinder.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
This commit is contained in:
Russell King 2011-01-28 21:00:39 +00:00
parent 5f2c1b30c7
commit 4e8d76373c
3 changed files with 157 additions and 68 deletions

View File

@ -346,7 +346,7 @@ config ARCH_FOOTBRIDGE
bool "FootBridge"
select CPU_SA110
select FOOTBRIDGE
select ARCH_USES_GETTIMEOFFSET
select GENERIC_CLOCKEVENTS
help
Support for systems based on the DC21285 companion chip
("FootBridge"), such as the Simtec CATS and the Rebel NetWinder.

View File

@ -4,10 +4,11 @@
* Copyright (C) 1998 Russell King.
* Copyright (C) 1998 Phil Blundell
*/
#include <linux/clockchips.h>
#include <linux/clocksource.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/spinlock.h>
#include <asm/irq.h>
@ -16,32 +17,76 @@
#include "common.h"
/*
* Footbridge timer 1 support.
*/
static unsigned long timer1_latch;
static unsigned long timer1_gettimeoffset (void)
static cycle_t cksrc_dc21285_read(struct clocksource *cs)
{
unsigned long value = timer1_latch - *CSR_TIMER1_VALUE;
return ((tick_nsec / 1000) * value) / timer1_latch;
return cs->mask - *CSR_TIMER2_VALUE;
}
static irqreturn_t
timer1_interrupt(int irq, void *dev_id)
static int cksrc_dc21285_enable(struct clocksource *cs)
{
*CSR_TIMER2_LOAD = cs->mask;
*CSR_TIMER2_CLR = 0;
*CSR_TIMER2_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_DIV16;
return 0;
}
static int cksrc_dc21285_disable(struct clocksource *cs)
{
*CSR_TIMER2_CNTL = 0;
}
static struct clocksource cksrc_dc21285 = {
.name = "dc21285_timer2",
.rating = 200,
.read = cksrc_dc21285_read,
.enable = cksrc_dc21285_enable,
.disable = cksrc_dc21285_disable,
.mask = CLOCKSOURCE_MASK(24),
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
static void ckevt_dc21285_set_mode(enum clock_event_mode mode,
struct clock_event_device *c)
{
switch (mode) {
case CLOCK_EVT_MODE_RESUME:
case CLOCK_EVT_MODE_PERIODIC:
*CSR_TIMER1_CLR = 0;
*CSR_TIMER1_LOAD = (mem_fclk_21285 + 8 * HZ) / (16 * HZ);
*CSR_TIMER1_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_AUTORELOAD |
TIMER_CNTL_DIV16;
break;
default:
*CSR_TIMER1_CNTL = 0;
break;
}
}
static struct clock_event_device ckevt_dc21285 = {
.name = "dc21285_timer1",
.features = CLOCK_EVT_FEAT_PERIODIC,
.rating = 200,
.irq = IRQ_TIMER1,
.set_mode = ckevt_dc21285_set_mode,
};
static irqreturn_t timer1_interrupt(int irq, void *dev_id)
{
struct clock_event_device *ce = dev_id;
*CSR_TIMER1_CLR = 0;
timer_tick();
ce->event_handler(ce);
return IRQ_HANDLED;
}
static struct irqaction footbridge_timer_irq = {
.name = "Timer1 timer tick",
.name = "dc21285_timer1",
.handler = timer1_interrupt,
.flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
.dev_id = &ckevt_dc21285,
};
/*
@ -49,16 +94,19 @@ static struct irqaction footbridge_timer_irq = {
*/
static void __init footbridge_timer_init(void)
{
timer1_latch = (mem_fclk_21285 + 8 * HZ) / (16 * HZ);
struct clock_event_device *ce = &ckevt_dc21285;
*CSR_TIMER1_CLR = 0;
*CSR_TIMER1_LOAD = timer1_latch;
*CSR_TIMER1_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_AUTORELOAD | TIMER_CNTL_DIV16;
clocksource_register_hz(&cksrc_dc21285, (mem_fclk_21285 + 8) / 16);
setup_irq(IRQ_TIMER1, &footbridge_timer_irq);
setup_irq(ce->irq, &footbridge_timer_irq);
clockevents_calc_mult_shift(ce, mem_fclk_21285, 5);
ce->max_delta_ns = clockevent_delta2ns(0xffffff, ce);
ce->min_delta_ns = clockevent_delta2ns(0x000004, ce);
clockevents_register_device(ce);
}
struct sys_timer footbridge_timer = {
.init = footbridge_timer_init,
.offset = timer1_gettimeoffset,
};

View File

@ -4,10 +4,13 @@
* Copyright (C) 1998 Russell King.
* Copyright (C) 1998 Phil Blundell
*/
#include <linux/clockchips.h>
#include <linux/clocksource.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/timex.h>
#include <asm/irq.h>
@ -15,77 +18,115 @@
#include "common.h"
/*
* ISA timer tick support
*/
#define mSEC_10_from_14 ((14318180 + 100) / 200)
#define PIT_MODE 0x43
#define PIT_CH0 0x40
static unsigned long isa_gettimeoffset(void)
#define PIT_LATCH ((PIT_TICK_RATE + HZ / 2) / HZ)
static cycle_t pit_read(struct clocksource *cs)
{
unsigned long flags;
static int old_count;
static u32 old_jifs;
int count;
u32 jifs;
static int count_p = (mSEC_10_from_14/6); /* for the first call after boot */
static unsigned long jiffies_p = 0;
raw_local_irq_save(flags);
/*
* cache volatile jiffies temporarily; we have IRQs turned off.
*/
unsigned long jiffies_t;
jifs = jiffies;
outb_p(0x00, PIT_MODE); /* latch the count */
count = inb_p(PIT_CH0); /* read the latched count */
count |= inb_p(PIT_CH0) << 8;
/* timer count may underflow right here */
outb_p(0x00, 0x43); /* latch the count ASAP */
if (count > old_count && jifs == old_jifs)
count = old_count;
count = inb_p(0x40); /* read the latched count */
old_count = count;
old_jifs = jifs;
/*
* We do this guaranteed double memory access instead of a _p
* postfix in the previous port access. Wheee, hackady hack
*/
jiffies_t = jiffies;
raw_local_irq_restore(flags);
count |= inb_p(0x40) << 8;
count = (PIT_LATCH - 1) - count;
/* Detect timer underflows. If we haven't had a timer tick since
the last time we were called, and time is apparently going
backwards, the counter must have wrapped during this routine. */
if ((jiffies_t == jiffies_p) && (count > count_p))
count -= (mSEC_10_from_14/6);
else
jiffies_p = jiffies_t;
count_p = count;
count = (((mSEC_10_from_14/6)-1) - count) * (tick_nsec / 1000);
count = (count + (mSEC_10_from_14/6)/2) / (mSEC_10_from_14/6);
return count;
return (cycle_t)(jifs * PIT_LATCH) + count;
}
static irqreturn_t
isa_timer_interrupt(int irq, void *dev_id)
static struct clocksource pit_cs = {
.name = "pit",
.rating = 110,
.read = pit_read,
.mask = CLOCKSOURCE_MASK(32),
};
static void pit_set_mode(enum clock_event_mode mode,
struct clock_event_device *evt)
{
timer_tick();
unsigned long flags;
raw_local_irq_save(flags);
switch (mode) {
case CLOCK_EVT_MODE_PERIODIC:
outb_p(0x34, PIT_MODE);
outb_p(PIT_LATCH & 0xff, PIT_CH0);
outb_p(PIT_LATCH >> 8, PIT_CH0);
break;
case CLOCK_EVT_MODE_SHUTDOWN:
case CLOCK_EVT_MODE_UNUSED:
outb_p(0x30, PIT_MODE);
outb_p(0, PIT_CH0);
outb_p(0, PIT_CH0);
break;
case CLOCK_EVT_MODE_ONESHOT:
case CLOCK_EVT_MODE_RESUME:
break;
}
local_irq_restore(flags);
}
static int pit_set_next_event(unsigned long delta,
struct clock_event_device *evt)
{
return 0;
}
static struct clock_event_device pit_ce = {
.name = "pit",
.features = CLOCK_EVT_FEAT_PERIODIC,
.set_mode = pit_set_mode,
.set_next_event = pit_set_next_event,
.shift = 32,
};
static irqreturn_t pit_timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *ce = dev_id;
ce->event_handler(ce);
return IRQ_HANDLED;
}
static struct irqaction isa_timer_irq = {
.name = "ISA timer tick",
.handler = isa_timer_interrupt,
static struct irqaction pit_timer_irq = {
.name = "pit",
.handler = pit_timer_interrupt,
.flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
.dev_id = &pit_ce,
};
static void __init isa_timer_init(void)
{
/* enable PIT timer */
/* set for periodic (4) and LSB/MSB write (0x30) */
outb(0x34, 0x43);
outb((mSEC_10_from_14/6) & 0xFF, 0x40);
outb((mSEC_10_from_14/6) >> 8, 0x40);
pit_ce.cpumask = cpumask_of(smp_processor_id());
pit_ce.mult = div_sc(PIT_TICK_RATE, NSEC_PER_SEC, pit_ce.shift);
pit_ce.max_delta_ns = clockevent_delta2ns(0x7fff, &pit_ce);
pit_ce.min_delta_ns = clockevent_delta2ns(0x000f, &pit_ce);
setup_irq(IRQ_ISA_TIMER, &isa_timer_irq);
clocksource_register_hz(&pit_cs, PIT_TICK_RATE);
setup_irq(pit_ce.irq, &pit_timer_irq);
clockevents_register_device(&pit_ce);
}
struct sys_timer isa_timer = {
.init = isa_timer_init,
.offset = isa_gettimeoffset,
};