linux/arch/mips/bcm63xx/timer.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) 2008 Maxime Bizon <mbizon@freebox.fr>
   7 */
   8
   9#include <linux/kernel.h>
  10#include <linux/err.h>
  11#include <linux/module.h>
  12#include <linux/spinlock.h>
  13#include <linux/interrupt.h>
  14#include <linux/clk.h>
  15#include <bcm63xx_cpu.h>
  16#include <bcm63xx_io.h>
  17#include <bcm63xx_timer.h>
  18#include <bcm63xx_regs.h>
  19
  20static DEFINE_RAW_SPINLOCK(timer_reg_lock);
  21static DEFINE_RAW_SPINLOCK(timer_data_lock);
  22static struct clk *periph_clk;
  23
  24static struct timer_data {
  25        void    (*cb)(void *);
  26        void    *data;
  27} timer_data[BCM63XX_TIMER_COUNT];
  28
  29static irqreturn_t timer_interrupt(int irq, void *dev_id)
  30{
  31        u32 stat;
  32        int i;
  33
  34        raw_spin_lock(&timer_reg_lock);
  35        stat = bcm_timer_readl(TIMER_IRQSTAT_REG);
  36        bcm_timer_writel(stat, TIMER_IRQSTAT_REG);
  37        raw_spin_unlock(&timer_reg_lock);
  38
  39        for (i = 0; i < BCM63XX_TIMER_COUNT; i++) {
  40                if (!(stat & TIMER_IRQSTAT_TIMER_CAUSE(i)))
  41                        continue;
  42
  43                raw_spin_lock(&timer_data_lock);
  44                if (!timer_data[i].cb) {
  45                        raw_spin_unlock(&timer_data_lock);
  46                        continue;
  47                }
  48
  49                timer_data[i].cb(timer_data[i].data);
  50                raw_spin_unlock(&timer_data_lock);
  51        }
  52
  53        return IRQ_HANDLED;
  54}
  55
  56int bcm63xx_timer_enable(int id)
  57{
  58        u32 reg;
  59        unsigned long flags;
  60
  61        if (id >= BCM63XX_TIMER_COUNT)
  62                return -EINVAL;
  63
  64        raw_spin_lock_irqsave(&timer_reg_lock, flags);
  65
  66        reg = bcm_timer_readl(TIMER_CTLx_REG(id));
  67        reg |= TIMER_CTL_ENABLE_MASK;
  68        bcm_timer_writel(reg, TIMER_CTLx_REG(id));
  69
  70        reg = bcm_timer_readl(TIMER_IRQSTAT_REG);
  71        reg |= TIMER_IRQSTAT_TIMER_IR_EN(id);
  72        bcm_timer_writel(reg, TIMER_IRQSTAT_REG);
  73
  74        raw_spin_unlock_irqrestore(&timer_reg_lock, flags);
  75        return 0;
  76}
  77
  78EXPORT_SYMBOL(bcm63xx_timer_enable);
  79
  80int bcm63xx_timer_disable(int id)
  81{
  82        u32 reg;
  83        unsigned long flags;
  84
  85        if (id >= BCM63XX_TIMER_COUNT)
  86                return -EINVAL;
  87
  88        raw_spin_lock_irqsave(&timer_reg_lock, flags);
  89
  90        reg = bcm_timer_readl(TIMER_CTLx_REG(id));
  91        reg &= ~TIMER_CTL_ENABLE_MASK;
  92        bcm_timer_writel(reg, TIMER_CTLx_REG(id));
  93
  94        reg = bcm_timer_readl(TIMER_IRQSTAT_REG);
  95        reg &= ~TIMER_IRQSTAT_TIMER_IR_EN(id);
  96        bcm_timer_writel(reg, TIMER_IRQSTAT_REG);
  97
  98        raw_spin_unlock_irqrestore(&timer_reg_lock, flags);
  99        return 0;
 100}
 101
 102EXPORT_SYMBOL(bcm63xx_timer_disable);
 103
 104int bcm63xx_timer_register(int id, void (*callback)(void *data), void *data)
 105{
 106        unsigned long flags;
 107        int ret;
 108
 109        if (id >= BCM63XX_TIMER_COUNT || !callback)
 110                return -EINVAL;
 111
 112        ret = 0;
 113        raw_spin_lock_irqsave(&timer_data_lock, flags);
 114        if (timer_data[id].cb) {
 115                ret = -EBUSY;
 116                goto out;
 117        }
 118
 119        timer_data[id].cb = callback;
 120        timer_data[id].data = data;
 121
 122out:
 123        raw_spin_unlock_irqrestore(&timer_data_lock, flags);
 124        return ret;
 125}
 126
 127EXPORT_SYMBOL(bcm63xx_timer_register);
 128
 129void bcm63xx_timer_unregister(int id)
 130{
 131        unsigned long flags;
 132
 133        if (id >= BCM63XX_TIMER_COUNT)
 134                return;
 135
 136        raw_spin_lock_irqsave(&timer_data_lock, flags);
 137        timer_data[id].cb = NULL;
 138        raw_spin_unlock_irqrestore(&timer_data_lock, flags);
 139}
 140
 141EXPORT_SYMBOL(bcm63xx_timer_unregister);
 142
 143unsigned int bcm63xx_timer_countdown(unsigned int countdown_us)
 144{
 145        return (clk_get_rate(periph_clk) / (1000 * 1000)) * countdown_us;
 146}
 147
 148EXPORT_SYMBOL(bcm63xx_timer_countdown);
 149
 150int bcm63xx_timer_set(int id, int monotonic, unsigned int countdown_us)
 151{
 152        u32 reg, countdown;
 153        unsigned long flags;
 154
 155        if (id >= BCM63XX_TIMER_COUNT)
 156                return -EINVAL;
 157
 158        countdown = bcm63xx_timer_countdown(countdown_us);
 159        if (countdown & ~TIMER_CTL_COUNTDOWN_MASK)
 160                return -EINVAL;
 161
 162        raw_spin_lock_irqsave(&timer_reg_lock, flags);
 163        reg = bcm_timer_readl(TIMER_CTLx_REG(id));
 164
 165        if (monotonic)
 166                reg &= ~TIMER_CTL_MONOTONIC_MASK;
 167        else
 168                reg |= TIMER_CTL_MONOTONIC_MASK;
 169
 170        reg &= ~TIMER_CTL_COUNTDOWN_MASK;
 171        reg |= countdown;
 172        bcm_timer_writel(reg, TIMER_CTLx_REG(id));
 173
 174        raw_spin_unlock_irqrestore(&timer_reg_lock, flags);
 175        return 0;
 176}
 177
 178EXPORT_SYMBOL(bcm63xx_timer_set);
 179
 180int bcm63xx_timer_init(void)
 181{
 182        int ret, irq;
 183        u32 reg;
 184
 185        reg = bcm_timer_readl(TIMER_IRQSTAT_REG);
 186        reg &= ~TIMER_IRQSTAT_TIMER0_IR_EN;
 187        reg &= ~TIMER_IRQSTAT_TIMER1_IR_EN;
 188        reg &= ~TIMER_IRQSTAT_TIMER2_IR_EN;
 189        bcm_timer_writel(reg, TIMER_IRQSTAT_REG);
 190
 191        periph_clk = clk_get(NULL, "periph");
 192        if (IS_ERR(periph_clk))
 193                return -ENODEV;
 194
 195        irq = bcm63xx_get_irq_number(IRQ_TIMER);
 196        ret = request_irq(irq, timer_interrupt, 0, "bcm63xx_timer", NULL);
 197        if (ret) {
 198                pr_err("%s: failed to register irq\n", __func__);
 199                return ret;
 200        }
 201
 202        return 0;
 203}
 204
 205arch_initcall(bcm63xx_timer_init);
 206