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 printk(KERN_ERR "bcm63xx_timer: failed to register irq\n"); 199 return ret; 200 } 201 202 return 0; 203} 204 205arch_initcall(bcm63xx_timer_init); 206