]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2529c3a3 XL |
2 | /* |
3 | * Freescale FlexTimer Module (FTM) timer driver. | |
4 | * | |
5 | * Copyright 2014 Freescale Semiconductor, Inc. | |
2529c3a3 XL |
6 | */ |
7 | ||
8 | #include <linux/clk.h> | |
9 | #include <linux/clockchips.h> | |
10 | #include <linux/clocksource.h> | |
11 | #include <linux/err.h> | |
12 | #include <linux/interrupt.h> | |
13 | #include <linux/io.h> | |
14 | #include <linux/of_address.h> | |
15 | #include <linux/of_irq.h> | |
16 | #include <linux/sched_clock.h> | |
17 | #include <linux/slab.h> | |
d4c5c462 | 18 | #include <linux/fsl/ftm.h> |
2529c3a3 | 19 | |
d4c5c462 | 20 | #define FTM_SC_CLK(c) ((c) << FTM_SC_CLK_MASK_SHIFT) |
2529c3a3 XL |
21 | |
22 | struct ftm_clock_device { | |
23 | void __iomem *clksrc_base; | |
24 | void __iomem *clkevt_base; | |
25 | unsigned long periodic_cyc; | |
26 | unsigned long ps; | |
27 | bool big_endian; | |
28 | }; | |
29 | ||
30 | static struct ftm_clock_device *priv; | |
31 | ||
32 | static inline u32 ftm_readl(void __iomem *addr) | |
33 | { | |
34 | if (priv->big_endian) | |
35 | return ioread32be(addr); | |
36 | else | |
37 | return ioread32(addr); | |
38 | } | |
39 | ||
40 | static inline void ftm_writel(u32 val, void __iomem *addr) | |
41 | { | |
42 | if (priv->big_endian) | |
43 | iowrite32be(val, addr); | |
44 | else | |
45 | iowrite32(val, addr); | |
46 | } | |
47 | ||
48 | static inline void ftm_counter_enable(void __iomem *base) | |
49 | { | |
50 | u32 val; | |
51 | ||
52 | /* select and enable counter clock source */ | |
53 | val = ftm_readl(base + FTM_SC); | |
54 | val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK); | |
55 | val |= priv->ps | FTM_SC_CLK(1); | |
56 | ftm_writel(val, base + FTM_SC); | |
57 | } | |
58 | ||
59 | static inline void ftm_counter_disable(void __iomem *base) | |
60 | { | |
61 | u32 val; | |
62 | ||
63 | /* disable counter clock source */ | |
64 | val = ftm_readl(base + FTM_SC); | |
65 | val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK); | |
66 | ftm_writel(val, base + FTM_SC); | |
67 | } | |
68 | ||
69 | static inline void ftm_irq_acknowledge(void __iomem *base) | |
70 | { | |
71 | u32 val; | |
72 | ||
73 | val = ftm_readl(base + FTM_SC); | |
74 | val &= ~FTM_SC_TOF; | |
75 | ftm_writel(val, base + FTM_SC); | |
76 | } | |
77 | ||
78 | static inline void ftm_irq_enable(void __iomem *base) | |
79 | { | |
80 | u32 val; | |
81 | ||
82 | val = ftm_readl(base + FTM_SC); | |
83 | val |= FTM_SC_TOIE; | |
84 | ftm_writel(val, base + FTM_SC); | |
85 | } | |
86 | ||
87 | static inline void ftm_irq_disable(void __iomem *base) | |
88 | { | |
89 | u32 val; | |
90 | ||
91 | val = ftm_readl(base + FTM_SC); | |
92 | val &= ~FTM_SC_TOIE; | |
93 | ftm_writel(val, base + FTM_SC); | |
94 | } | |
95 | ||
96 | static inline void ftm_reset_counter(void __iomem *base) | |
97 | { | |
98 | /* | |
99 | * The CNT register contains the FTM counter value. | |
100 | * Reset clears the CNT register. Writing any value to COUNT | |
101 | * updates the counter with its initial value, CNTIN. | |
102 | */ | |
103 | ftm_writel(0x00, base + FTM_CNT); | |
104 | } | |
105 | ||
bd859a44 | 106 | static u64 notrace ftm_read_sched_clock(void) |
2529c3a3 XL |
107 | { |
108 | return ftm_readl(priv->clksrc_base + FTM_CNT); | |
109 | } | |
110 | ||
111 | static int ftm_set_next_event(unsigned long delta, | |
112 | struct clock_event_device *unused) | |
113 | { | |
114 | /* | |
115 | * The CNNIN and MOD are all double buffer registers, writing | |
116 | * to the MOD register latches the value into a buffer. The MOD | |
117 | * register is updated with the value of its write buffer with | |
118 | * the following scenario: | |
119 | * a, the counter source clock is diabled. | |
120 | */ | |
121 | ftm_counter_disable(priv->clkevt_base); | |
122 | ||
123 | /* Force the value of CNTIN to be loaded into the FTM counter */ | |
124 | ftm_reset_counter(priv->clkevt_base); | |
125 | ||
126 | /* | |
127 | * The counter increments until the value of MOD is reached, | |
128 | * at which point the counter is reloaded with the value of CNTIN. | |
129 | * The TOF (the overflow flag) bit is set when the FTM counter | |
130 | * changes from MOD to CNTIN. So we should using the delta - 1. | |
131 | */ | |
132 | ftm_writel(delta - 1, priv->clkevt_base + FTM_MOD); | |
133 | ||
134 | ftm_counter_enable(priv->clkevt_base); | |
135 | ||
136 | ftm_irq_enable(priv->clkevt_base); | |
137 | ||
138 | return 0; | |
139 | } | |
140 | ||
73766340 | 141 | static int ftm_set_oneshot(struct clock_event_device *evt) |
2529c3a3 | 142 | { |
73766340 VK |
143 | ftm_counter_disable(priv->clkevt_base); |
144 | return 0; | |
145 | } | |
146 | ||
147 | static int ftm_set_periodic(struct clock_event_device *evt) | |
148 | { | |
149 | ftm_set_next_event(priv->periodic_cyc, evt); | |
150 | return 0; | |
2529c3a3 XL |
151 | } |
152 | ||
153 | static irqreturn_t ftm_evt_interrupt(int irq, void *dev_id) | |
154 | { | |
155 | struct clock_event_device *evt = dev_id; | |
156 | ||
157 | ftm_irq_acknowledge(priv->clkevt_base); | |
158 | ||
73766340 | 159 | if (likely(clockevent_state_oneshot(evt))) { |
2529c3a3 XL |
160 | ftm_irq_disable(priv->clkevt_base); |
161 | ftm_counter_disable(priv->clkevt_base); | |
162 | } | |
163 | ||
164 | evt->event_handler(evt); | |
165 | ||
166 | return IRQ_HANDLED; | |
167 | } | |
168 | ||
169 | static struct clock_event_device ftm_clockevent = { | |
73766340 VK |
170 | .name = "Freescale ftm timer", |
171 | .features = CLOCK_EVT_FEAT_PERIODIC | | |
172 | CLOCK_EVT_FEAT_ONESHOT, | |
173 | .set_state_periodic = ftm_set_periodic, | |
174 | .set_state_oneshot = ftm_set_oneshot, | |
175 | .set_next_event = ftm_set_next_event, | |
176 | .rating = 300, | |
2529c3a3 XL |
177 | }; |
178 | ||
179 | static struct irqaction ftm_timer_irq = { | |
180 | .name = "Freescale ftm timer", | |
181 | .flags = IRQF_TIMER | IRQF_IRQPOLL, | |
182 | .handler = ftm_evt_interrupt, | |
183 | .dev_id = &ftm_clockevent, | |
184 | }; | |
185 | ||
186 | static int __init ftm_clockevent_init(unsigned long freq, int irq) | |
187 | { | |
188 | int err; | |
189 | ||
190 | ftm_writel(0x00, priv->clkevt_base + FTM_CNTIN); | |
dde7632e | 191 | ftm_writel(~0u, priv->clkevt_base + FTM_MOD); |
2529c3a3 XL |
192 | |
193 | ftm_reset_counter(priv->clkevt_base); | |
194 | ||
195 | err = setup_irq(irq, &ftm_timer_irq); | |
196 | if (err) { | |
197 | pr_err("ftm: setup irq failed: %d\n", err); | |
198 | return err; | |
199 | } | |
200 | ||
201 | ftm_clockevent.cpumask = cpumask_of(0); | |
202 | ftm_clockevent.irq = irq; | |
203 | ||
204 | clockevents_config_and_register(&ftm_clockevent, | |
205 | freq / (1 << priv->ps), | |
206 | 1, 0xffff); | |
207 | ||
208 | ftm_counter_enable(priv->clkevt_base); | |
209 | ||
210 | return 0; | |
211 | } | |
212 | ||
213 | static int __init ftm_clocksource_init(unsigned long freq) | |
214 | { | |
215 | int err; | |
216 | ||
217 | ftm_writel(0x00, priv->clksrc_base + FTM_CNTIN); | |
dde7632e | 218 | ftm_writel(~0u, priv->clksrc_base + FTM_MOD); |
2529c3a3 XL |
219 | |
220 | ftm_reset_counter(priv->clksrc_base); | |
221 | ||
222 | sched_clock_register(ftm_read_sched_clock, 16, freq / (1 << priv->ps)); | |
223 | err = clocksource_mmio_init(priv->clksrc_base + FTM_CNT, "fsl-ftm", | |
224 | freq / (1 << priv->ps), 300, 16, | |
225 | clocksource_mmio_readl_up); | |
226 | if (err) { | |
227 | pr_err("ftm: init clock source mmio failed: %d\n", err); | |
228 | return err; | |
229 | } | |
230 | ||
231 | ftm_counter_enable(priv->clksrc_base); | |
232 | ||
233 | return 0; | |
234 | } | |
235 | ||
236 | static int __init __ftm_clk_init(struct device_node *np, char *cnt_name, | |
237 | char *ftm_name) | |
238 | { | |
239 | struct clk *clk; | |
240 | int err; | |
241 | ||
242 | clk = of_clk_get_by_name(np, cnt_name); | |
243 | if (IS_ERR(clk)) { | |
244 | pr_err("ftm: Cannot get \"%s\": %ld\n", cnt_name, PTR_ERR(clk)); | |
245 | return PTR_ERR(clk); | |
246 | } | |
247 | err = clk_prepare_enable(clk); | |
248 | if (err) { | |
249 | pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n", | |
250 | cnt_name, err); | |
251 | return err; | |
252 | } | |
253 | ||
254 | clk = of_clk_get_by_name(np, ftm_name); | |
255 | if (IS_ERR(clk)) { | |
256 | pr_err("ftm: Cannot get \"%s\": %ld\n", ftm_name, PTR_ERR(clk)); | |
257 | return PTR_ERR(clk); | |
258 | } | |
259 | err = clk_prepare_enable(clk); | |
260 | if (err) | |
261 | pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n", | |
262 | ftm_name, err); | |
263 | ||
264 | return clk_get_rate(clk); | |
265 | } | |
266 | ||
267 | static unsigned long __init ftm_clk_init(struct device_node *np) | |
268 | { | |
f287eb90 | 269 | long freq; |
2529c3a3 XL |
270 | |
271 | freq = __ftm_clk_init(np, "ftm-evt-counter-en", "ftm-evt"); | |
272 | if (freq <= 0) | |
273 | return 0; | |
274 | ||
275 | freq = __ftm_clk_init(np, "ftm-src-counter-en", "ftm-src"); | |
276 | if (freq <= 0) | |
277 | return 0; | |
278 | ||
279 | return freq; | |
280 | } | |
281 | ||
282 | static int __init ftm_calc_closest_round_cyc(unsigned long freq) | |
283 | { | |
284 | priv->ps = 0; | |
285 | ||
286 | /* The counter register is only using the lower 16 bits, and | |
287 | * if the 'freq' value is to big here, then the periodic_cyc | |
288 | * may exceed 0xFFFF. | |
289 | */ | |
290 | do { | |
291 | priv->periodic_cyc = DIV_ROUND_CLOSEST(freq, | |
292 | HZ * (1 << priv->ps++)); | |
293 | } while (priv->periodic_cyc > 0xFFFF); | |
294 | ||
295 | if (priv->ps > FTM_PS_MAX) { | |
296 | pr_err("ftm: the prescaler is %lu > %d\n", | |
297 | priv->ps, FTM_PS_MAX); | |
298 | return -EINVAL; | |
299 | } | |
300 | ||
301 | return 0; | |
302 | } | |
303 | ||
17c8669d | 304 | static int __init ftm_timer_init(struct device_node *np) |
2529c3a3 XL |
305 | { |
306 | unsigned long freq; | |
17c8669d | 307 | int ret, irq; |
2529c3a3 XL |
308 | |
309 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | |
310 | if (!priv) | |
17c8669d | 311 | return -ENOMEM; |
2529c3a3 | 312 | |
17c8669d | 313 | ret = -ENXIO; |
2529c3a3 XL |
314 | priv->clkevt_base = of_iomap(np, 0); |
315 | if (!priv->clkevt_base) { | |
316 | pr_err("ftm: unable to map event timer registers\n"); | |
b70957f6 | 317 | goto err_clkevt; |
2529c3a3 XL |
318 | } |
319 | ||
320 | priv->clksrc_base = of_iomap(np, 1); | |
321 | if (!priv->clksrc_base) { | |
322 | pr_err("ftm: unable to map source timer registers\n"); | |
b70957f6 | 323 | goto err_clksrc; |
2529c3a3 XL |
324 | } |
325 | ||
17c8669d | 326 | ret = -EINVAL; |
2529c3a3 XL |
327 | irq = irq_of_parse_and_map(np, 0); |
328 | if (irq <= 0) { | |
329 | pr_err("ftm: unable to get IRQ from DT, %d\n", irq); | |
330 | goto err; | |
331 | } | |
332 | ||
333 | priv->big_endian = of_property_read_bool(np, "big-endian"); | |
334 | ||
335 | freq = ftm_clk_init(np); | |
336 | if (!freq) | |
337 | goto err; | |
338 | ||
17c8669d DL |
339 | ret = ftm_calc_closest_round_cyc(freq); |
340 | if (ret) | |
2529c3a3 XL |
341 | goto err; |
342 | ||
17c8669d DL |
343 | ret = ftm_clocksource_init(freq); |
344 | if (ret) | |
2529c3a3 XL |
345 | goto err; |
346 | ||
17c8669d DL |
347 | ret = ftm_clockevent_init(freq, irq); |
348 | if (ret) | |
2529c3a3 XL |
349 | goto err; |
350 | ||
17c8669d | 351 | return 0; |
2529c3a3 XL |
352 | |
353 | err: | |
b70957f6 AY |
354 | iounmap(priv->clksrc_base); |
355 | err_clksrc: | |
356 | iounmap(priv->clkevt_base); | |
357 | err_clkevt: | |
2529c3a3 | 358 | kfree(priv); |
17c8669d | 359 | return ret; |
2529c3a3 | 360 | } |
17273395 | 361 | TIMER_OF_DECLARE(flextimer, "fsl,ftm-timer", ftm_timer_init); |