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: | |
4bf07f65 | 119 | * a, the counter source clock is disabled. |
2529c3a3 XL |
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 | ||
2529c3a3 XL |
179 | static int __init ftm_clockevent_init(unsigned long freq, int irq) |
180 | { | |
181 | int err; | |
182 | ||
183 | ftm_writel(0x00, priv->clkevt_base + FTM_CNTIN); | |
dde7632e | 184 | ftm_writel(~0u, priv->clkevt_base + FTM_MOD); |
2529c3a3 XL |
185 | |
186 | ftm_reset_counter(priv->clkevt_base); | |
187 | ||
cc2550b4 | 188 | err = request_irq(irq, ftm_evt_interrupt, IRQF_TIMER | IRQF_IRQPOLL, |
189 | "Freescale ftm timer", &ftm_clockevent); | |
2529c3a3 XL |
190 | if (err) { |
191 | pr_err("ftm: setup irq failed: %d\n", err); | |
192 | return err; | |
193 | } | |
194 | ||
195 | ftm_clockevent.cpumask = cpumask_of(0); | |
196 | ftm_clockevent.irq = irq; | |
197 | ||
198 | clockevents_config_and_register(&ftm_clockevent, | |
199 | freq / (1 << priv->ps), | |
200 | 1, 0xffff); | |
201 | ||
202 | ftm_counter_enable(priv->clkevt_base); | |
203 | ||
204 | return 0; | |
205 | } | |
206 | ||
207 | static int __init ftm_clocksource_init(unsigned long freq) | |
208 | { | |
209 | int err; | |
210 | ||
211 | ftm_writel(0x00, priv->clksrc_base + FTM_CNTIN); | |
dde7632e | 212 | ftm_writel(~0u, priv->clksrc_base + FTM_MOD); |
2529c3a3 XL |
213 | |
214 | ftm_reset_counter(priv->clksrc_base); | |
215 | ||
216 | sched_clock_register(ftm_read_sched_clock, 16, freq / (1 << priv->ps)); | |
217 | err = clocksource_mmio_init(priv->clksrc_base + FTM_CNT, "fsl-ftm", | |
218 | freq / (1 << priv->ps), 300, 16, | |
219 | clocksource_mmio_readl_up); | |
220 | if (err) { | |
221 | pr_err("ftm: init clock source mmio failed: %d\n", err); | |
222 | return err; | |
223 | } | |
224 | ||
225 | ftm_counter_enable(priv->clksrc_base); | |
226 | ||
227 | return 0; | |
228 | } | |
229 | ||
230 | static int __init __ftm_clk_init(struct device_node *np, char *cnt_name, | |
231 | char *ftm_name) | |
232 | { | |
233 | struct clk *clk; | |
234 | int err; | |
235 | ||
236 | clk = of_clk_get_by_name(np, cnt_name); | |
237 | if (IS_ERR(clk)) { | |
238 | pr_err("ftm: Cannot get \"%s\": %ld\n", cnt_name, PTR_ERR(clk)); | |
239 | return PTR_ERR(clk); | |
240 | } | |
241 | err = clk_prepare_enable(clk); | |
242 | if (err) { | |
243 | pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n", | |
244 | cnt_name, err); | |
245 | return err; | |
246 | } | |
247 | ||
248 | clk = of_clk_get_by_name(np, ftm_name); | |
249 | if (IS_ERR(clk)) { | |
250 | pr_err("ftm: Cannot get \"%s\": %ld\n", ftm_name, PTR_ERR(clk)); | |
251 | return PTR_ERR(clk); | |
252 | } | |
253 | err = clk_prepare_enable(clk); | |
254 | if (err) | |
255 | pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n", | |
256 | ftm_name, err); | |
257 | ||
258 | return clk_get_rate(clk); | |
259 | } | |
260 | ||
261 | static unsigned long __init ftm_clk_init(struct device_node *np) | |
262 | { | |
f287eb90 | 263 | long freq; |
2529c3a3 XL |
264 | |
265 | freq = __ftm_clk_init(np, "ftm-evt-counter-en", "ftm-evt"); | |
266 | if (freq <= 0) | |
267 | return 0; | |
268 | ||
269 | freq = __ftm_clk_init(np, "ftm-src-counter-en", "ftm-src"); | |
270 | if (freq <= 0) | |
271 | return 0; | |
272 | ||
273 | return freq; | |
274 | } | |
275 | ||
276 | static int __init ftm_calc_closest_round_cyc(unsigned long freq) | |
277 | { | |
278 | priv->ps = 0; | |
279 | ||
280 | /* The counter register is only using the lower 16 bits, and | |
281 | * if the 'freq' value is to big here, then the periodic_cyc | |
282 | * may exceed 0xFFFF. | |
283 | */ | |
284 | do { | |
285 | priv->periodic_cyc = DIV_ROUND_CLOSEST(freq, | |
286 | HZ * (1 << priv->ps++)); | |
287 | } while (priv->periodic_cyc > 0xFFFF); | |
288 | ||
289 | if (priv->ps > FTM_PS_MAX) { | |
290 | pr_err("ftm: the prescaler is %lu > %d\n", | |
291 | priv->ps, FTM_PS_MAX); | |
292 | return -EINVAL; | |
293 | } | |
294 | ||
295 | return 0; | |
296 | } | |
297 | ||
17c8669d | 298 | static int __init ftm_timer_init(struct device_node *np) |
2529c3a3 XL |
299 | { |
300 | unsigned long freq; | |
17c8669d | 301 | int ret, irq; |
2529c3a3 XL |
302 | |
303 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | |
304 | if (!priv) | |
17c8669d | 305 | return -ENOMEM; |
2529c3a3 | 306 | |
17c8669d | 307 | ret = -ENXIO; |
2529c3a3 XL |
308 | priv->clkevt_base = of_iomap(np, 0); |
309 | if (!priv->clkevt_base) { | |
310 | pr_err("ftm: unable to map event timer registers\n"); | |
b70957f6 | 311 | goto err_clkevt; |
2529c3a3 XL |
312 | } |
313 | ||
314 | priv->clksrc_base = of_iomap(np, 1); | |
315 | if (!priv->clksrc_base) { | |
316 | pr_err("ftm: unable to map source timer registers\n"); | |
b70957f6 | 317 | goto err_clksrc; |
2529c3a3 XL |
318 | } |
319 | ||
17c8669d | 320 | ret = -EINVAL; |
2529c3a3 XL |
321 | irq = irq_of_parse_and_map(np, 0); |
322 | if (irq <= 0) { | |
323 | pr_err("ftm: unable to get IRQ from DT, %d\n", irq); | |
324 | goto err; | |
325 | } | |
326 | ||
327 | priv->big_endian = of_property_read_bool(np, "big-endian"); | |
328 | ||
329 | freq = ftm_clk_init(np); | |
330 | if (!freq) | |
331 | goto err; | |
332 | ||
17c8669d DL |
333 | ret = ftm_calc_closest_round_cyc(freq); |
334 | if (ret) | |
2529c3a3 XL |
335 | goto err; |
336 | ||
17c8669d DL |
337 | ret = ftm_clocksource_init(freq); |
338 | if (ret) | |
2529c3a3 XL |
339 | goto err; |
340 | ||
17c8669d DL |
341 | ret = ftm_clockevent_init(freq, irq); |
342 | if (ret) | |
2529c3a3 XL |
343 | goto err; |
344 | ||
17c8669d | 345 | return 0; |
2529c3a3 XL |
346 | |
347 | err: | |
b70957f6 AY |
348 | iounmap(priv->clksrc_base); |
349 | err_clksrc: | |
350 | iounmap(priv->clkevt_base); | |
351 | err_clkevt: | |
2529c3a3 | 352 | kfree(priv); |
17c8669d | 353 | return ret; |
2529c3a3 | 354 | } |
17273395 | 355 | TIMER_OF_DECLARE(flextimer, "fsl,ftm-timer", ftm_timer_init); |