1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Freescale FlexTimer Module (FTM) timer driver. |
4 | * |
5 | * Copyright 2014 Freescale Semiconductor, Inc. |
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> |
18 | #include <linux/fsl/ftm.h> |
19 | |
20 | #define FTM_SC_CLK(c) ((c) << FTM_SC_CLK_MASK_SHIFT) |
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(addr: 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, addr: 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(addr: base + FTM_SC); |
65 | val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK); |
66 | ftm_writel(val, addr: base + FTM_SC); |
67 | } |
68 | |
69 | static inline void ftm_irq_acknowledge(void __iomem *base) |
70 | { |
71 | u32 val; |
72 | |
73 | val = ftm_readl(addr: base + FTM_SC); |
74 | val &= ~FTM_SC_TOF; |
75 | ftm_writel(val, addr: base + FTM_SC); |
76 | } |
77 | |
78 | static inline void ftm_irq_enable(void __iomem *base) |
79 | { |
80 | u32 val; |
81 | |
82 | val = ftm_readl(addr: base + FTM_SC); |
83 | val |= FTM_SC_TOIE; |
84 | ftm_writel(val, addr: base + FTM_SC); |
85 | } |
86 | |
87 | static inline void ftm_irq_disable(void __iomem *base) |
88 | { |
89 | u32 val; |
90 | |
91 | val = ftm_readl(addr: base + FTM_SC); |
92 | val &= ~FTM_SC_TOIE; |
93 | ftm_writel(val, addr: 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(val: 0x00, addr: base + FTM_CNT); |
104 | } |
105 | |
106 | static u64 notrace ftm_read_sched_clock(void) |
107 | { |
108 | return ftm_readl(addr: 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 disabled. |
120 | */ |
121 | ftm_counter_disable(base: priv->clkevt_base); |
122 | |
123 | /* Force the value of CNTIN to be loaded into the FTM counter */ |
124 | ftm_reset_counter(base: 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(val: delta - 1, addr: priv->clkevt_base + FTM_MOD); |
133 | |
134 | ftm_counter_enable(base: priv->clkevt_base); |
135 | |
136 | ftm_irq_enable(base: priv->clkevt_base); |
137 | |
138 | return 0; |
139 | } |
140 | |
141 | static int ftm_set_oneshot(struct clock_event_device *evt) |
142 | { |
143 | ftm_counter_disable(base: 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(delta: priv->periodic_cyc, unused: evt); |
150 | return 0; |
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(base: priv->clkevt_base); |
158 | |
159 | if (likely(clockevent_state_oneshot(evt))) { |
160 | ftm_irq_disable(base: priv->clkevt_base); |
161 | ftm_counter_disable(base: 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 = { |
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, |
177 | }; |
178 | |
179 | static int __init ftm_clockevent_init(unsigned long freq, int irq) |
180 | { |
181 | int err; |
182 | |
183 | ftm_writel(val: 0x00, addr: priv->clkevt_base + FTM_CNTIN); |
184 | ftm_writel(val: ~0u, addr: priv->clkevt_base + FTM_MOD); |
185 | |
186 | ftm_reset_counter(base: priv->clkevt_base); |
187 | |
188 | err = request_irq(irq, handler: ftm_evt_interrupt, IRQF_TIMER | IRQF_IRQPOLL, |
189 | name: "Freescale ftm timer" , dev: &ftm_clockevent); |
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(dev: &ftm_clockevent, |
199 | freq: freq / (1 << priv->ps), |
200 | min_delta: 1, max_delta: 0xffff); |
201 | |
202 | ftm_counter_enable(base: 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(val: 0x00, addr: priv->clksrc_base + FTM_CNTIN); |
212 | ftm_writel(val: ~0u, addr: priv->clksrc_base + FTM_MOD); |
213 | |
214 | ftm_reset_counter(base: priv->clksrc_base); |
215 | |
216 | sched_clock_register(read: ftm_read_sched_clock, bits: 16, rate: 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(base: 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, name: cnt_name); |
237 | if (IS_ERR(ptr: clk)) { |
238 | pr_err("ftm: Cannot get \"%s\": %ld\n" , cnt_name, PTR_ERR(clk)); |
239 | return PTR_ERR(ptr: 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, name: ftm_name); |
249 | if (IS_ERR(ptr: clk)) { |
250 | pr_err("ftm: Cannot get \"%s\": %ld\n" , ftm_name, PTR_ERR(clk)); |
251 | return PTR_ERR(ptr: 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 | { |
263 | long freq; |
264 | |
265 | freq = __ftm_clk_init(np, cnt_name: "ftm-evt-counter-en" , ftm_name: "ftm-evt" ); |
266 | if (freq <= 0) |
267 | return 0; |
268 | |
269 | freq = __ftm_clk_init(np, cnt_name: "ftm-src-counter-en" , ftm_name: "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 | |
298 | static int __init ftm_timer_init(struct device_node *np) |
299 | { |
300 | unsigned long freq; |
301 | int ret, irq; |
302 | |
303 | priv = kzalloc(size: sizeof(*priv), GFP_KERNEL); |
304 | if (!priv) |
305 | return -ENOMEM; |
306 | |
307 | ret = -ENXIO; |
308 | priv->clkevt_base = of_iomap(node: np, index: 0); |
309 | if (!priv->clkevt_base) { |
310 | pr_err("ftm: unable to map event timer registers\n" ); |
311 | goto err_clkevt; |
312 | } |
313 | |
314 | priv->clksrc_base = of_iomap(node: np, index: 1); |
315 | if (!priv->clksrc_base) { |
316 | pr_err("ftm: unable to map source timer registers\n" ); |
317 | goto err_clksrc; |
318 | } |
319 | |
320 | ret = -EINVAL; |
321 | irq = irq_of_parse_and_map(node: np, index: 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, propname: "big-endian" ); |
328 | |
329 | freq = ftm_clk_init(np); |
330 | if (!freq) |
331 | goto err; |
332 | |
333 | ret = ftm_calc_closest_round_cyc(freq); |
334 | if (ret) |
335 | goto err; |
336 | |
337 | ret = ftm_clocksource_init(freq); |
338 | if (ret) |
339 | goto err; |
340 | |
341 | ret = ftm_clockevent_init(freq, irq); |
342 | if (ret) |
343 | goto err; |
344 | |
345 | return 0; |
346 | |
347 | err: |
348 | iounmap(addr: priv->clksrc_base); |
349 | err_clksrc: |
350 | iounmap(addr: priv->clkevt_base); |
351 | err_clkevt: |
352 | kfree(objp: priv); |
353 | return ret; |
354 | } |
355 | TIMER_OF_DECLARE(flextimer, "fsl,ftm-timer" , ftm_timer_init); |
356 | |