/* MN10300 clockevents * * Copyright (C) 2010 Red Hat, Inc. All Rights Reserved. * Written by Mark Salter (msalter@redhat.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public Licence * as published by the Free Software Foundation; either version * 2 of the Licence, or (at your option) any later version. */ #include <linux/clockchips.h> #include <linux/interrupt.h> #include <linux/percpu.h> #include <linux/smp.h> #include <asm/timex.h> #include "internal.h" #ifdef CONFIG_SMP #if (CONFIG_NR_CPUS > 2) && !defined(CONFIG_GEENERIC_CLOCKEVENTS_BROADCAST) #error "This doesn't scale well! Need per-core local timers." #endif #else /* CONFIG_SMP */ #define stop_jiffies_counter1() #define reload_jiffies_counter1(x) #define TMJC1IRQ TMJCIRQ #endif static int next_event(unsigned long delta, struct clock_event_device *evt) { unsigned int cpu = smp_processor_id(); if (cpu == 0) { stop_jiffies_counter(); reload_jiffies_counter(delta - 1); } else { stop_jiffies_counter1(); reload_jiffies_counter1(delta - 1); } return 0; } static void set_clock_mode(enum clock_event_mode mode, struct clock_event_device *evt) { /* Nothing to do ... */ } static DEFINE_PER_CPU(struct clock_event_device, mn10300_clockevent_device); static DEFINE_PER_CPU(struct irqaction, timer_irq); static irqreturn_t timer_interrupt(int irq, void *dev_id) { struct clock_event_device *cd; unsigned int cpu = smp_processor_id(); if (cpu == 0) stop_jiffies_counter(); else stop_jiffies_counter1(); cd = &per_cpu(mn10300_clockevent_device, cpu); cd->event_handler(cd); return IRQ_HANDLED; } static void event_handler(struct clock_event_device *dev) { } static inline void setup_jiffies_interrupt(int irq, struct irqaction *action) { u16 tmp; setup_irq(irq, action); set_intr_level(irq, NUM2GxICR_LEVEL(CONFIG_TIMER_IRQ_LEVEL)); GxICR(irq) |= GxICR_ENABLE | GxICR_DETECT | GxICR_REQUEST; tmp = GxICR(irq); } int __init init_clockevents(void) { struct clock_event_device *cd; struct irqaction *iact; unsigned int cpu = smp_processor_id(); cd = &per_cpu(mn10300_clockevent_device, cpu); if (cpu == 0) { stop_jiffies_counter(); cd->irq = TMJCIRQ; } else { stop_jiffies_counter1(); cd->irq = TMJC1IRQ; } cd->name = "Timestamp"; cd->features = CLOCK_EVT_FEAT_ONESHOT; /* Calculate shift/mult. We want to spawn at least 1 second */ clockevents_calc_mult_shift(cd, MN10300_JCCLK, 1); /* Calculate the min / max delta */ cd->max_delta_ns = clockevent_delta2ns(TMJCBR_MAX, cd); cd->min_delta_ns = clockevent_delta2ns(100, cd); cd->rating = 200; cd->cpumask = cpumask_of(smp_processor_id()); cd->set_mode = set_clock_mode; cd->event_handler = event_handler; cd->set_next_event = next_event; iact = &per_cpu(timer_irq, cpu); iact->flags = IRQF_SHARED | IRQF_TIMER; iact->handler = timer_interrupt; clockevents_register_device(cd); #if defined(CONFIG_SMP) && !defined(CONFIG_GENERIC_CLOCKEVENTS_BROADCAST) /* setup timer irq affinity so it only runs on this cpu */ { struct irq_data *data; data = irq_get_irq_data(cd->irq); cpumask_copy(data->affinity, cpumask_of(cpu)); iact->flags |= IRQF_NOBALANCING; } #endif if (cpu == 0) { reload_jiffies_counter(MN10300_JC_PER_HZ - 1); iact->name = "CPU0 Timer"; } else { reload_jiffies_counter1(MN10300_JC_PER_HZ - 1); iact->name = "CPU1 Timer"; } setup_jiffies_interrupt(cd->irq, iact); return 0; }