linux/arch/mips/kernel/cevt-r4k.c
<<
>>
Prefs
   1/*
   2 * This file is subject to the terms and conditions of the GNU General Public
   3 * License.  See the file "COPYING" in the main directory of this archive
   4 * for more details.
   5 *
   6 * Copyright (C) 2007 MIPS Technologies, Inc.
   7 * Copyright (C) 2007 Ralf Baechle <ralf@linux-mips.org>
   8 */
   9#include <linux/clockchips.h>
  10#include <linux/interrupt.h>
  11#include <linux/percpu.h>
  12
  13#include <asm/smtc_ipi.h>
  14#include <asm/time.h>
  15#include <asm/cevt-r4k.h>
  16
  17/*
  18 * The SMTC Kernel for the 34K, 1004K, et. al. replaces several
  19 * of these routines with SMTC-specific variants.
  20 */
  21
  22#ifndef CONFIG_MIPS_MT_SMTC
  23
  24static int mips_next_event(unsigned long delta,
  25                           struct clock_event_device *evt)
  26{
  27        unsigned int cnt;
  28        int res;
  29
  30        cnt = read_c0_count();
  31        cnt += delta;
  32        write_c0_compare(cnt);
  33        res = ((int)(read_c0_count() - cnt) > 0) ? -ETIME : 0;
  34        return res;
  35}
  36
  37#endif /* CONFIG_MIPS_MT_SMTC */
  38
  39void mips_set_clock_mode(enum clock_event_mode mode,
  40                                struct clock_event_device *evt)
  41{
  42        /* Nothing to do ...  */
  43}
  44
  45DEFINE_PER_CPU(struct clock_event_device, mips_clockevent_device);
  46int cp0_timer_irq_installed;
  47
  48#ifndef CONFIG_MIPS_MT_SMTC
  49
  50irqreturn_t c0_compare_interrupt(int irq, void *dev_id)
  51{
  52        const int r2 = cpu_has_mips_r2;
  53        struct clock_event_device *cd;
  54        int cpu = smp_processor_id();
  55
  56        /*
  57         * Suckage alert:
  58         * Before R2 of the architecture there was no way to see if a
  59         * performance counter interrupt was pending, so we have to run
  60         * the performance counter interrupt handler anyway.
  61         */
  62        if (handle_perf_irq(r2))
  63                goto out;
  64
  65        /*
  66         * The same applies to performance counter interrupts.  But with the
  67         * above we now know that the reason we got here must be a timer
  68         * interrupt.  Being the paranoiacs we are we check anyway.
  69         */
  70        if (!r2 || (read_c0_cause() & (1 << 30))) {
  71                /* Clear Count/Compare Interrupt */
  72                write_c0_compare(read_c0_compare());
  73                cd = &per_cpu(mips_clockevent_device, cpu);
  74                cd->event_handler(cd);
  75        }
  76
  77out:
  78        return IRQ_HANDLED;
  79}
  80
  81#endif /* Not CONFIG_MIPS_MT_SMTC */
  82
  83struct irqaction c0_compare_irqaction = {
  84        .handler = c0_compare_interrupt,
  85        .flags = IRQF_DISABLED | IRQF_PERCPU,
  86        .name = "timer",
  87};
  88
  89
  90void mips_event_handler(struct clock_event_device *dev)
  91{
  92}
  93
  94/*
  95 * FIXME: This doesn't hold for the relocated E9000 compare interrupt.
  96 */
  97static int c0_compare_int_pending(void)
  98{
  99        return (read_c0_cause() >> cp0_compare_irq) & 0x100;
 100}
 101
 102/*
 103 * Compare interrupt can be routed and latched outside the core,
 104 * so a single execution hazard barrier may not be enough to give
 105 * it time to clear as seen in the Cause register.  4 time the
 106 * pipeline depth seems reasonably conservative, and empirically
 107 * works better in configurations with high CPU/bus clock ratios.
 108 */
 109
 110#define compare_change_hazard() \
 111        do { \
 112                irq_disable_hazard(); \
 113                irq_disable_hazard(); \
 114                irq_disable_hazard(); \
 115                irq_disable_hazard(); \
 116        } while (0)
 117
 118int c0_compare_int_usable(void)
 119{
 120        unsigned int delta;
 121        unsigned int cnt;
 122
 123        /*
 124         * IP7 already pending?  Try to clear it by acking the timer.
 125         */
 126        if (c0_compare_int_pending()) {
 127                write_c0_compare(read_c0_count());
 128                compare_change_hazard();
 129                if (c0_compare_int_pending())
 130                        return 0;
 131        }
 132
 133        for (delta = 0x10; delta <= 0x400000; delta <<= 1) {
 134                cnt = read_c0_count();
 135                cnt += delta;
 136                write_c0_compare(cnt);
 137                compare_change_hazard();
 138                if ((int)(read_c0_count() - cnt) < 0)
 139                    break;
 140                /* increase delta if the timer was already expired */
 141        }
 142
 143        while ((int)(read_c0_count() - cnt) <= 0)
 144                ;       /* Wait for expiry  */
 145
 146        compare_change_hazard();
 147        if (!c0_compare_int_pending())
 148                return 0;
 149
 150        write_c0_compare(read_c0_count());
 151        compare_change_hazard();
 152        if (c0_compare_int_pending())
 153                return 0;
 154
 155        /*
 156         * Feels like a real count / compare timer.
 157         */
 158        return 1;
 159}
 160
 161#ifndef CONFIG_MIPS_MT_SMTC
 162
 163int __cpuinit r4k_clockevent_init(void)
 164{
 165        uint64_t mips_freq = mips_hpt_frequency;
 166        unsigned int cpu = smp_processor_id();
 167        struct clock_event_device *cd;
 168        unsigned int irq;
 169
 170        if (!cpu_has_counter || !mips_hpt_frequency)
 171                return -ENXIO;
 172
 173        if (!c0_compare_int_usable())
 174                return -ENXIO;
 175
 176        /*
 177         * With vectored interrupts things are getting platform specific.
 178         * get_c0_compare_int is a hook to allow a platform to return the
 179         * interrupt number of it's liking.
 180         */
 181        irq = MIPS_CPU_IRQ_BASE + cp0_compare_irq;
 182        if (get_c0_compare_int)
 183                irq = get_c0_compare_int();
 184
 185        cd = &per_cpu(mips_clockevent_device, cpu);
 186
 187        cd->name                = "MIPS";
 188        cd->features            = CLOCK_EVT_FEAT_ONESHOT;
 189
 190        /* Calculate the min / max delta */
 191        cd->mult        = div_sc((unsigned long) mips_freq, NSEC_PER_SEC, 32);
 192        cd->shift               = 32;
 193        cd->max_delta_ns        = clockevent_delta2ns(0x7fffffff, cd);
 194        cd->min_delta_ns        = clockevent_delta2ns(0x300, cd);
 195
 196        cd->rating              = 300;
 197        cd->irq                 = irq;
 198        cd->cpumask             = cpumask_of(cpu);
 199        cd->set_next_event      = mips_next_event;
 200        cd->set_mode            = mips_set_clock_mode;
 201        cd->event_handler       = mips_event_handler;
 202
 203        clockevents_register_device(cd);
 204
 205        if (cp0_timer_irq_installed)
 206                return 0;
 207
 208        cp0_timer_irq_installed = 1;
 209
 210        setup_irq(irq, &c0_compare_irqaction);
 211
 212        return 0;
 213}
 214
 215#endif /* Not CONFIG_MIPS_MT_SMTC */
 216