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(clk: 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, id: "periph" ); |
193 | if (IS_ERR(ptr: periph_clk)) |
194 | return -ENODEV; |
195 | |
196 | irq = bcm63xx_get_irq_number(IRQ_TIMER); |
197 | ret = request_irq(irq, handler: timer_interrupt, flags: 0, name: "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 | |