Skip to content

Commit

Permalink
sparc32: generic clockevent support
Browse files Browse the repository at this point in the history
The kernel uses l14 timers as clockevents. l10 timer is used
as clocksource if platform master_l10_counter isn't constantly
zero. The clocksource is continuous, so it's possible to use
high resolution timers. l10 timer is also used as clockevent
on UP configurations.

This realization is for sun4m, sun4d, sun4c, microsparc-IIep
and LEON platforms. The appropriate LEON changes was made by
Konrad Eisele.

In case of sun4m's oneshot mode, profile irq is zeroed in
smp4m_percpu_timer_interrupt(). It is maybe
needless (double, triple etc overflow does nothing).

sun4d is able to have oneshot mode too, but I haven't
any way to test it. So code of its percpu timer handler
is made as much equal to the current code as possible.

The patch is tested on sun4m box in SMP mode by me,
and tested by Konrad on leon in up mode (leon smp
is broken atm - due to other reasons).

Signed-off-by: Tkhai Kirill <tkhai@yandex.ru>
Tested-by: Konrad Eisele <konrad@gaisler.com> [leon up]
[sam: revised patch to provide generic support for leon]
Signed-off-by: Sam Ravnborg <sam@ravnborg.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Tkhai Kirill authored and David S. Miller committed Apr 15, 2012
1 parent 472bc4f commit 62f0828
Show file tree
Hide file tree
Showing 17 changed files with 362 additions and 189 deletions.
7 changes: 1 addition & 6 deletions arch/sparc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,12 @@ config BITS
default 32 if SPARC32
default 64 if SPARC64

config ARCH_USES_GETTIMEOFFSET
bool
default y if SPARC32

config GENERIC_CMOS_UPDATE
bool
default y

config GENERIC_CLOCKEVENTS
bool
default y if SPARC64
def_bool y

config IOMMU_HELPER
bool
Expand Down
1 change: 0 additions & 1 deletion arch/sparc/include/asm/cpudata_32.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
typedef struct {
unsigned long udelay_val;
unsigned long clock_tick;
unsigned int multiplier;
unsigned int counter;
#ifdef CONFIG_SMP
unsigned int irq_resched_count;
Expand Down
2 changes: 1 addition & 1 deletion arch/sparc/include/asm/leon.h
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ extern void leon_update_virq_handling(unsigned int virq,
const char *name, int do_ack);
extern void leon_clear_clock_irq(void);
extern void leon_load_profile_irq(int cpu, unsigned int limit);
extern void leon_init_timers(irq_handler_t counter_fn);
extern void leon_init_timers(void);
extern void leon_clear_clock_irq(void);
extern void leon_load_profile_irq(int cpu, unsigned int limit);
extern void leon_trans_init(struct device_node *dp);
Expand Down
29 changes: 29 additions & 0 deletions arch/sparc/include/asm/timer_32.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,40 @@
#ifndef _SPARC_TIMER_H
#define _SPARC_TIMER_H

#include <linux/clocksource.h>
#include <linux/irqreturn.h>

#include <asm-generic/percpu.h>

#include <asm/cpu_type.h> /* For SUN4M_NCPUS */
#include <asm/btfixup.h>

#define SBUS_CLOCK_RATE 2000000 /* 2MHz */
#define TIMER_VALUE_SHIFT 9
#define TIMER_VALUE_MASK 0x3fffff
#define TIMER_LIMIT_BIT (1 << 31) /* Bit 31 in Counter-Timer register */

/* The counter timer register has the value offset by 9 bits.
* From sun4m manual:
* When a counter reaches the value in the corresponding limit register,
* the Limit bit is set and the counter is set to 500 nS (i.e. 0x00000200).
*
* To compensate for this add one to the value.
*/
static inline unsigned int timer_value(unsigned int value)
{
return (value + 1) << TIMER_VALUE_SHIFT;
}

extern __volatile__ unsigned int *master_l10_counter;

extern irqreturn_t notrace timer_interrupt(int dummy, void *dev_id);

#ifdef CONFIG_SMP
DECLARE_PER_CPU(struct clock_event_device, sparc32_clockevent);
extern void register_percpu_ce(int cpu);
#endif

/* FIXME: Make do_[gs]ettimeofday btfixup calls */
struct timespec;
BTFIXUPDEF_CALL(int, bus_do_settimeofday, struct timespec *tv)
Expand Down
1 change: 0 additions & 1 deletion arch/sparc/include/asm/timex_32.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,4 @@
typedef unsigned long cycles_t;
#define get_cycles() (0)

extern u32 (*do_arch_gettimeoffset)(void);
#endif
19 changes: 18 additions & 1 deletion arch/sparc/kernel/irq.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,32 @@ struct sun4m_irq_global {
extern struct sun4m_irq_percpu __iomem *sun4m_irq_percpu[SUN4M_NCPUS];
extern struct sun4m_irq_global __iomem *sun4m_irq_global;

/* The following definitions describe the individual platform features: */
#define FEAT_L10_CLOCKSOURCE (1 << 0) /* L10 timer is used as a clocksource */
#define FEAT_L10_CLOCKEVENT (1 << 1) /* L10 timer is used as a clockevent */
#define FEAT_L14_ONESHOT (1 << 2) /* L14 timer clockevent can oneshot */

/*
* Platform specific configuration
* The individual platforms assign their platform
* specifics in their init functions.
*/
struct sparc_config {
void (*init_timers)(irq_handler_t);
void (*init_timers)(void);
unsigned int (*build_device_irq)(struct platform_device *op,
unsigned int real_irq);

/* generic clockevent features - see FEAT_* above */
int features;

/* clock rate used for clock event timer */
int clock_rate;

/* one period for clock source timer */
unsigned int cs_period;

/* function to obtain offsett for cs period */
unsigned int (*get_cycles_offset)(void);
};
extern struct sparc_config sparc_config;

Expand Down
2 changes: 0 additions & 2 deletions arch/sparc/kernel/kernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ extern void init_IRQ(void);
extern void sun4c_init_IRQ(void);

/* sun4m_irq.c */
extern unsigned int lvl14_resolution;

extern void sun4m_init_IRQ(void);
extern void sun4m_unmask_profile_irq(void);
extern void sun4m_clear_profile_irq(int cpu);
Expand Down
49 changes: 45 additions & 4 deletions arch/sparc/kernel/leon_kernel.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include <linux/of_platform.h>
#include <linux/interrupt.h>
#include <linux/of_device.h>
#include <linux/clocksource.h>
#include <linux/clockchips.h>

#include <asm/oplib.h>
#include <asm/timer.h>
Expand Down Expand Up @@ -250,7 +252,38 @@ void leon_update_virq_handling(unsigned int virq,
irq_set_chip_data(virq, (void *)mask);
}

void __init leon_init_timers(irq_handler_t counter_fn)
static u32 leon_cycles_offset(void)
{
u32 rld, val, off;
rld = LEON3_BYPASS_LOAD_PA(&leon3_gptimer_regs->e[leon3_gptimer_idx].rld);
val = LEON3_BYPASS_LOAD_PA(&leon3_gptimer_regs->e[leon3_gptimer_idx].val);
off = rld - val;
return rld - val;
}

#ifdef CONFIG_SMP

/* smp clockevent irq */
irqreturn_t leon_percpu_timer_ce_interrupt(int irq, void *unused)
{
struct clock_event_device *ce;
int cpu = smp_processor_id();

leon_clear_profile_irq(cpu);

ce = &per_cpu(sparc32_clockevent, cpu);

irq_enter();
if (ce->event_handler)
ce->event_handler(ce);
irq_exit();

return IRQ_HANDLED;
}

#endif /* CONFIG_SMP */

void __init leon_init_timers(void)
{
int irq, eirq;
struct device_node *rootnp, *np, *nnp;
Expand All @@ -260,6 +293,14 @@ void __init leon_init_timers(irq_handler_t counter_fn)
int ampopts;
int err;

sparc_config.get_cycles_offset = leon_cycles_offset;
sparc_config.cs_period = 1000000 / HZ;
sparc_config.features |= FEAT_L10_CLOCKSOURCE;

#ifndef CONFIG_SMP
sparc_config.features |= FEAT_L10_CLOCKEVENT;
#endif

leondebug_irq_disable = 0;
leon_debug_irqout = 0;
master_l10_counter = (unsigned int *)&dummy_master_l10_counter;
Expand Down Expand Up @@ -369,7 +410,7 @@ void __init leon_init_timers(irq_handler_t counter_fn)
leon_eirq_setup(eirq);

irq = _leon_build_device_irq(NULL, leon3_gptimer_irq+leon3_gptimer_idx);
err = request_irq(irq, counter_fn, IRQF_TIMER, "timer", NULL);
err = request_irq(irq, timer_interrupt, IRQF_TIMER, "timer", NULL);
if (err) {
printk(KERN_ERR "unable to attach timer IRQ%d\n", irq);
prom_halt();
Expand Down Expand Up @@ -401,7 +442,7 @@ void __init leon_init_timers(irq_handler_t counter_fn)
/* Install per-cpu IRQ handler for broadcasted ticker */
irq = leon_build_device_irq(leon3_ticker_irq, handle_percpu_irq,
"per-cpu", 0);
err = request_irq(irq, leon_percpu_timer_interrupt,
err = request_irq(irq, leon_percpu_timer_ce_interrupt,
IRQF_PERCPU | IRQF_TIMER, "ticker",
NULL);
if (err) {
Expand All @@ -428,7 +469,6 @@ void leon_clear_clock_irq(void)

void leon_load_profile_irq(int cpu, unsigned int limit)
{
BUG();
}

void __init leon_trans_init(struct device_node *dp)
Expand Down Expand Up @@ -496,6 +536,7 @@ void __init leon_init_IRQ(void)
{
sparc_config.init_timers = leon_init_timers;
sparc_config.build_device_irq = _leon_build_device_irq;
sparc_config.clock_rate = 1000000;

BTFIXUPSET_CALL(clear_clock_irq, leon_clear_clock_irq,
BTFIXUPCALL_NORM);
Expand Down
34 changes: 4 additions & 30 deletions arch/sparc/kernel/leon_smp.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include <linux/pm.h>
#include <linux/delay.h>
#include <linux/gfp.h>
#include <linux/cpu.h>
#include <linux/clockchips.h>

#include <asm/cacheflush.h>
#include <asm/tlbflush.h>
Expand All @@ -42,6 +44,7 @@
#include <asm/asi.h>
#include <asm/leon.h>
#include <asm/leon_amba.h>
#include <asm/timer.h>

#include "kernel.h"

Expand All @@ -68,8 +71,6 @@ static inline unsigned long do_swap(volatile unsigned long *ptr,
return val;
}

static void smp_setup_percpu_timer(void);

void __cpuinit leon_callin(void)
{
int cpuid = hard_smpleon_processor_id();
Expand All @@ -79,7 +80,7 @@ void __cpuinit leon_callin(void)
leon_configure_cache_smp();

/* Get our local ticker going. */
smp_setup_percpu_timer();
register_percpu_ce(cpuid);

calibrate_delay();
smp_store_cpu_info(cpuid);
Expand Down Expand Up @@ -196,7 +197,6 @@ void __init leon_boot_cpus(void)
leon_smp_setbroadcast(1 << LEON3_IRQ_TICKER);

leon_configure_cache_smp();
smp_setup_percpu_timer();
local_flush_cache_all();

}
Expand Down Expand Up @@ -489,32 +489,6 @@ void leon_cross_call_irq(void)
ccall_info.processors_out[i] = 1;
}

irqreturn_t leon_percpu_timer_interrupt(int irq, void *unused)
{
int cpu = smp_processor_id();

leon_clear_profile_irq(cpu);

profile_tick(CPU_PROFILING);

if (!--prof_counter(cpu)) {
int user = user_mode(get_irq_regs());

update_process_times(user);

prof_counter(cpu) = prof_multiplier(cpu);
}

return IRQ_HANDLED;
}

static void __init smp_setup_percpu_timer(void)
{
int cpu = smp_processor_id();

prof_counter(cpu) = prof_multiplier(cpu) = 1;
}

void __init leon_blackbox_id(unsigned *addr)
{
int rd = *addr & 0x3e000000;
Expand Down
48 changes: 26 additions & 22 deletions arch/sparc/kernel/pcic.c
Original file line number Diff line number Diff line change
Expand Up @@ -703,31 +703,28 @@ static void pcic_clear_clock_irq(void)
pcic_timer_dummy = readl(pcic0.pcic_regs+PCI_SYS_LIMIT);
}

static irqreturn_t pcic_timer_handler (int irq, void *h)
/* CPU frequency is 100 MHz, timer increments every 4 CPU clocks */
#define USECS_PER_JIFFY (1000000 / HZ)
#define TICK_TIMER_LIMIT ((100 * 1000000 / 4) / HZ)

static unsigned int pcic_cycles_offset(void)
{
pcic_clear_clock_irq();
xtime_update(1);
#ifndef CONFIG_SMP
update_process_times(user_mode(get_irq_regs()));
#endif
return IRQ_HANDLED;
}
u32 value, count;

#define USECS_PER_JIFFY 10000 /* We have 100HZ "standard" timer for sparc */
#define TICK_TIMER_LIMIT ((100*1000000/4)/100)
value = readl(pcic0.pcic_regs + PCI_SYS_COUNTER);
count = value & ~PCI_SYS_COUNTER_OVERFLOW;

u32 pci_gettimeoffset(void)
{
if (value & PCI_SYS_COUNTER_OVERFLOW)
count += TICK_TIMER_LIMIT;
/*
* We divide all by 100
* We divide all by HZ
* to have microsecond resolution and to avoid overflow
*/
unsigned long count =
readl(pcic0.pcic_regs+PCI_SYS_COUNTER) & ~PCI_SYS_COUNTER_OVERFLOW;
count = ((count/100)*USECS_PER_JIFFY) / (TICK_TIMER_LIMIT/100);
return count * 1000;
}
count = ((count / HZ) * USECS_PER_JIFFY) / (TICK_TIMER_LIMIT / HZ);

/* Coordinate with the fact that timer_cs rate is 2MHz */
return count * 2;
}

void __init pci_time_init(void)
{
Expand All @@ -736,9 +733,16 @@ void __init pci_time_init(void)
int timer_irq, irq;
int err;

do_arch_gettimeoffset = pci_gettimeoffset;

btfixup();
#ifndef CONFIG_SMP
/*
* It's in SBUS dimension, because timer_cs is in this dimension.
* We take into account this in pcic_cycles_offset()
*/
timer_cs_period = SBUS_CLOCK_RATE / HZ;
sparc_config.features |= FEAT_L10_CLOCKEVENT;
#endif
sparc_config.features |= FEAT_L10_CLOCKSOURCE;
sparc_config.get_cycles_offset = pcic_cycles_offset;

writel (TICK_TIMER_LIMIT, pcic->pcic_regs+PCI_SYS_LIMIT);
/* PROM should set appropriate irq */
Expand All @@ -747,7 +751,7 @@ void __init pci_time_init(void)
writel (PCI_COUNTER_IRQ_SET(timer_irq, 0),
pcic->pcic_regs+PCI_COUNTER_IRQ);
irq = pcic_build_device_irq(NULL, timer_irq);
err = request_irq(irq, pcic_timer_handler,
err = request_irq(irq, timer_interrupt,
IRQF_TIMER, "timer", NULL);
if (err) {
prom_printf("time_init: unable to attach IRQ%d\n", timer_irq);
Expand Down
Loading

0 comments on commit 62f0828

Please sign in to comment.