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