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
6 * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
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>
20 static DEFINE_SPINLOCK(timer_reg_lock
);
21 static DEFINE_SPINLOCK(timer_data_lock
);
22 static struct clk
*periph_clk
;
24 static struct timer_data
{
27 } timer_data
[BCM63XX_TIMER_COUNT
];
29 static irqreturn_t
timer_interrupt(int irq
, void *dev_id
)
34 spin_lock(&timer_reg_lock
);
35 stat
= bcm_timer_readl(TIMER_IRQSTAT_REG
);
36 bcm_timer_writel(stat
, TIMER_IRQSTAT_REG
);
37 spin_unlock(&timer_reg_lock
);
39 for (i
= 0; i
< BCM63XX_TIMER_COUNT
; i
++) {
40 if (!(stat
& TIMER_IRQSTAT_TIMER_CAUSE(i
)))
43 spin_lock(&timer_data_lock
);
44 if (!timer_data
[i
].cb
) {
45 spin_unlock(&timer_data_lock
);
49 timer_data
[i
].cb(timer_data
[i
].data
);
50 spin_unlock(&timer_data_lock
);
56 int bcm63xx_timer_enable(int id
)
61 if (id
>= BCM63XX_TIMER_COUNT
)
64 spin_lock_irqsave(&timer_reg_lock
, flags
);
66 reg
= bcm_timer_readl(TIMER_CTLx_REG(id
));
67 reg
|= TIMER_CTL_ENABLE_MASK
;
68 bcm_timer_writel(reg
, TIMER_CTLx_REG(id
));
70 reg
= bcm_timer_readl(TIMER_IRQSTAT_REG
);
71 reg
|= TIMER_IRQSTAT_TIMER_IR_EN(id
);
72 bcm_timer_writel(reg
, TIMER_IRQSTAT_REG
);
74 spin_unlock_irqrestore(&timer_reg_lock
, flags
);
78 EXPORT_SYMBOL(bcm63xx_timer_enable
);
80 int bcm63xx_timer_disable(int id
)
85 if (id
>= BCM63XX_TIMER_COUNT
)
88 spin_lock_irqsave(&timer_reg_lock
, flags
);
90 reg
= bcm_timer_readl(TIMER_CTLx_REG(id
));
91 reg
&= ~TIMER_CTL_ENABLE_MASK
;
92 bcm_timer_writel(reg
, TIMER_CTLx_REG(id
));
94 reg
= bcm_timer_readl(TIMER_IRQSTAT_REG
);
95 reg
&= ~TIMER_IRQSTAT_TIMER_IR_EN(id
);
96 bcm_timer_writel(reg
, TIMER_IRQSTAT_REG
);
98 spin_unlock_irqrestore(&timer_reg_lock
, flags
);
102 EXPORT_SYMBOL(bcm63xx_timer_disable
);
104 int bcm63xx_timer_register(int id
, void (*callback
)(void *data
), void *data
)
109 if (id
>= BCM63XX_TIMER_COUNT
|| !callback
)
113 spin_lock_irqsave(&timer_data_lock
, flags
);
114 if (timer_data
[id
].cb
) {
119 timer_data
[id
].cb
= callback
;
120 timer_data
[id
].data
= data
;
123 spin_unlock_irqrestore(&timer_data_lock
, flags
);
127 EXPORT_SYMBOL(bcm63xx_timer_register
);
129 void bcm63xx_timer_unregister(int id
)
133 if (id
>= BCM63XX_TIMER_COUNT
)
136 spin_lock_irqsave(&timer_data_lock
, flags
);
137 timer_data
[id
].cb
= NULL
;
138 spin_unlock_irqrestore(&timer_data_lock
, flags
);
141 EXPORT_SYMBOL(bcm63xx_timer_unregister
);
143 unsigned int bcm63xx_timer_countdown(unsigned int countdown_us
)
145 return (clk_get_rate(periph_clk
) / (1000 * 1000)) * countdown_us
;
148 EXPORT_SYMBOL(bcm63xx_timer_countdown
);
150 int bcm63xx_timer_set(int id
, int monotonic
, unsigned int countdown_us
)
155 if (id
>= BCM63XX_TIMER_COUNT
)
158 countdown
= bcm63xx_timer_countdown(countdown_us
);
159 if (countdown
& ~TIMER_CTL_COUNTDOWN_MASK
)
162 spin_lock_irqsave(&timer_reg_lock
, flags
);
163 reg
= bcm_timer_readl(TIMER_CTLx_REG(id
));
166 reg
&= ~TIMER_CTL_MONOTONIC_MASK
;
168 reg
|= TIMER_CTL_MONOTONIC_MASK
;
170 reg
&= ~TIMER_CTL_COUNTDOWN_MASK
;
172 bcm_timer_writel(reg
, TIMER_CTLx_REG(id
));
174 spin_unlock_irqrestore(&timer_reg_lock
, flags
);
178 EXPORT_SYMBOL(bcm63xx_timer_set
);
180 int bcm63xx_timer_init(void)
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
);
191 periph_clk
= clk_get(NULL
, "periph");
192 if (IS_ERR(periph_clk
))
195 irq
= bcm63xx_get_irq_number(IRQ_TIMER
);
196 ret
= request_irq(irq
, timer_interrupt
, 0, "bcm63xx_timer", NULL
);
198 printk(KERN_ERR
"bcm63xx_timer: failed to register irq\n");
205 arch_initcall(bcm63xx_timer_init
);