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