aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXiubo Li <Li.Xiubo@freescale.com>2014-05-23 04:12:04 -0400
committerDaniel Lezcano <daniel.lezcano@linaro.org>2014-05-23 04:12:04 -0400
commit2529c3a330797000d699d70c9a65b8525c6652de (patch)
tree262217477e6775a2321ef29558604669afa88638
parent07513e1330a90253c564e2b9258f9cd12e1eb7ba (diff)
clocksource: Add Freescale FlexTimer Module (FTM) timer support
The Freescale FlexTimer Module time reference is a 16-bit counter that can be used as an unsigned or signed increase counter. CNTIN defines the starting value of the count and MOD defines the final value of the count. The value of CNTIN is loaded into the FTM counter, and the counter increments until the value of MOD is reached, at which point the counter is reloaded with the value of CNTIN. That's also when an overflow interrupt will be generated. Here using the 'evt' prefix or postfix as clock event device and the 'src' as clock source device. Signed-off-by: Xiubo Li <Li.Xiubo@freescale.com> Cc: Shawn Guo <shawn.guo@linaro.org> Cc: Jingchang Lu <b35083@freescale.com> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
-rw-r--r--drivers/clocksource/Kconfig5
-rw-r--r--drivers/clocksource/Makefile1
-rw-r--r--drivers/clocksource/fsl_ftm_timer.c367
3 files changed, 373 insertions, 0 deletions
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 96918e1f26a3..04377675c3fa 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -136,6 +136,11 @@ config CLKSRC_SAMSUNG_PWM
136 for all devicetree enabled platforms. This driver will be 136 for all devicetree enabled platforms. This driver will be
137 needed only on systems that do not have the Exynos MCT available. 137 needed only on systems that do not have the Exynos MCT available.
138 138
139config FSL_FTM_TIMER
140 bool
141 help
142 Support for Freescale FlexTimer Module (FTM) timer.
143
139config VF_PIT_TIMER 144config VF_PIT_TIMER
140 bool 145 bool
141 help 146 help
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 98cb6c51aa87..0770916a818e 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_CADENCE_TTC_TIMER) += cadence_ttc_timer.o
31obj-$(CONFIG_CLKSRC_EFM32) += time-efm32.o 31obj-$(CONFIG_CLKSRC_EFM32) += time-efm32.o
32obj-$(CONFIG_CLKSRC_EXYNOS_MCT) += exynos_mct.o 32obj-$(CONFIG_CLKSRC_EXYNOS_MCT) += exynos_mct.o
33obj-$(CONFIG_CLKSRC_SAMSUNG_PWM) += samsung_pwm_timer.o 33obj-$(CONFIG_CLKSRC_SAMSUNG_PWM) += samsung_pwm_timer.o
34obj-$(CONFIG_FSL_FTM_TIMER) += fsl_ftm_timer.o
34obj-$(CONFIG_VF_PIT_TIMER) += vf_pit_timer.o 35obj-$(CONFIG_VF_PIT_TIMER) += vf_pit_timer.o
35obj-$(CONFIG_CLKSRC_QCOM) += qcom-timer.o 36obj-$(CONFIG_CLKSRC_QCOM) += qcom-timer.o
36 37
diff --git a/drivers/clocksource/fsl_ftm_timer.c b/drivers/clocksource/fsl_ftm_timer.c
new file mode 100644
index 000000000000..454227d4f895
--- /dev/null
+++ b/drivers/clocksource/fsl_ftm_timer.c
@@ -0,0 +1,367 @@
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
23#define FTM_SC 0x00
24#define FTM_SC_CLK_SHIFT 3
25#define FTM_SC_CLK_MASK (0x3 << FTM_SC_CLK_SHIFT)
26#define FTM_SC_CLK(c) ((c) << FTM_SC_CLK_SHIFT)
27#define FTM_SC_PS_MASK 0x7
28#define FTM_SC_TOIE BIT(6)
29#define FTM_SC_TOF BIT(7)
30
31#define FTM_CNT 0x04
32#define FTM_MOD 0x08
33#define FTM_CNTIN 0x4C
34
35#define FTM_PS_MAX 7
36
37struct ftm_clock_device {
38 void __iomem *clksrc_base;
39 void __iomem *clkevt_base;
40 unsigned long periodic_cyc;
41 unsigned long ps;
42 bool big_endian;
43};
44
45static struct ftm_clock_device *priv;
46
47static inline u32 ftm_readl(void __iomem *addr)
48{
49 if (priv->big_endian)
50 return ioread32be(addr);
51 else
52 return ioread32(addr);
53}
54
55static inline void ftm_writel(u32 val, void __iomem *addr)
56{
57 if (priv->big_endian)
58 iowrite32be(val, addr);
59 else
60 iowrite32(val, addr);
61}
62
63static inline void ftm_counter_enable(void __iomem *base)
64{
65 u32 val;
66
67 /* select and enable counter clock source */
68 val = ftm_readl(base + FTM_SC);
69 val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);
70 val |= priv->ps | FTM_SC_CLK(1);
71 ftm_writel(val, base + FTM_SC);
72}
73
74static inline void ftm_counter_disable(void __iomem *base)
75{
76 u32 val;
77
78 /* disable counter clock source */
79 val = ftm_readl(base + FTM_SC);
80 val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);
81 ftm_writel(val, base + FTM_SC);
82}
83
84static inline void ftm_irq_acknowledge(void __iomem *base)
85{
86 u32 val;
87
88 val = ftm_readl(base + FTM_SC);
89 val &= ~FTM_SC_TOF;
90 ftm_writel(val, base + FTM_SC);
91}
92
93static inline void ftm_irq_enable(void __iomem *base)
94{
95 u32 val;
96
97 val = ftm_readl(base + FTM_SC);
98 val |= FTM_SC_TOIE;
99 ftm_writel(val, base + FTM_SC);
100}
101
102static inline void ftm_irq_disable(void __iomem *base)
103{
104 u32 val;
105
106 val = ftm_readl(base + FTM_SC);
107 val &= ~FTM_SC_TOIE;
108 ftm_writel(val, base + FTM_SC);
109}
110
111static inline void ftm_reset_counter(void __iomem *base)
112{
113 /*
114 * The CNT register contains the FTM counter value.
115 * Reset clears the CNT register. Writing any value to COUNT
116 * updates the counter with its initial value, CNTIN.
117 */
118 ftm_writel(0x00, base + FTM_CNT);
119}
120
121static u64 ftm_read_sched_clock(void)
122{
123 return ftm_readl(priv->clksrc_base + FTM_CNT);
124}
125
126static int ftm_set_next_event(unsigned long delta,
127 struct clock_event_device *unused)
128{
129 /*
130 * The CNNIN and MOD are all double buffer registers, writing
131 * to the MOD register latches the value into a buffer. The MOD
132 * register is updated with the value of its write buffer with
133 * the following scenario:
134 * a, the counter source clock is diabled.
135 */
136 ftm_counter_disable(priv->clkevt_base);
137
138 /* Force the value of CNTIN to be loaded into the FTM counter */
139 ftm_reset_counter(priv->clkevt_base);
140
141 /*
142 * The counter increments until the value of MOD is reached,
143 * at which point the counter is reloaded with the value of CNTIN.
144 * The TOF (the overflow flag) bit is set when the FTM counter
145 * changes from MOD to CNTIN. So we should using the delta - 1.
146 */
147 ftm_writel(delta - 1, priv->clkevt_base + FTM_MOD);
148
149 ftm_counter_enable(priv->clkevt_base);
150
151 ftm_irq_enable(priv->clkevt_base);
152
153 return 0;
154}
155
156static void ftm_set_mode(enum clock_event_mode mode,
157 struct clock_event_device *evt)
158{
159 switch (mode) {
160 case CLOCK_EVT_MODE_PERIODIC:
161 ftm_set_next_event(priv->periodic_cyc, evt);
162 break;
163 case CLOCK_EVT_MODE_ONESHOT:
164 ftm_counter_disable(priv->clkevt_base);
165 break;
166 default:
167 return;
168 }
169}
170
171static irqreturn_t ftm_evt_interrupt(int irq, void *dev_id)
172{
173 struct clock_event_device *evt = dev_id;
174
175 ftm_irq_acknowledge(priv->clkevt_base);
176
177 if (likely(evt->mode == CLOCK_EVT_MODE_ONESHOT)) {
178 ftm_irq_disable(priv->clkevt_base);
179 ftm_counter_disable(priv->clkevt_base);
180 }
181
182 evt->event_handler(evt);
183
184 return IRQ_HANDLED;
185}
186
187static struct clock_event_device ftm_clockevent = {
188 .name = "Freescale ftm timer",
189 .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
190 .set_mode = ftm_set_mode,
191 .set_next_event = ftm_set_next_event,
192 .rating = 300,
193};
194
195static struct irqaction ftm_timer_irq = {
196 .name = "Freescale ftm timer",
197 .flags = IRQF_TIMER | IRQF_IRQPOLL,
198 .handler = ftm_evt_interrupt,
199 .dev_id = &ftm_clockevent,
200};
201
202static int __init ftm_clockevent_init(unsigned long freq, int irq)
203{
204 int err;
205
206 ftm_writel(0x00, priv->clkevt_base + FTM_CNTIN);
207 ftm_writel(~0UL, priv->clkevt_base + FTM_MOD);
208
209 ftm_reset_counter(priv->clkevt_base);
210
211 err = setup_irq(irq, &ftm_timer_irq);
212 if (err) {
213 pr_err("ftm: setup irq failed: %d\n", err);
214 return err;
215 }
216
217 ftm_clockevent.cpumask = cpumask_of(0);
218 ftm_clockevent.irq = irq;
219
220 clockevents_config_and_register(&ftm_clockevent,
221 freq / (1 << priv->ps),
222 1, 0xffff);
223
224 ftm_counter_enable(priv->clkevt_base);
225
226 return 0;
227}
228
229static int __init ftm_clocksource_init(unsigned long freq)
230{
231 int err;
232
233 ftm_writel(0x00, priv->clksrc_base + FTM_CNTIN);
234 ftm_writel(~0UL, priv->clksrc_base + FTM_MOD);
235
236 ftm_reset_counter(priv->clksrc_base);
237
238 sched_clock_register(ftm_read_sched_clock, 16, freq / (1 << priv->ps));
239 err = clocksource_mmio_init(priv->clksrc_base + FTM_CNT, "fsl-ftm",
240 freq / (1 << priv->ps), 300, 16,
241 clocksource_mmio_readl_up);
242 if (err) {
243 pr_err("ftm: init clock source mmio failed: %d\n", err);
244 return err;
245 }
246
247 ftm_counter_enable(priv->clksrc_base);
248
249 return 0;
250}
251
252static int __init __ftm_clk_init(struct device_node *np, char *cnt_name,
253 char *ftm_name)
254{
255 struct clk *clk;
256 int err;
257
258 clk = of_clk_get_by_name(np, cnt_name);
259 if (IS_ERR(clk)) {
260 pr_err("ftm: Cannot get \"%s\": %ld\n", cnt_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 cnt_name, err);
267 return err;
268 }
269
270 clk = of_clk_get_by_name(np, ftm_name);
271 if (IS_ERR(clk)) {
272 pr_err("ftm: Cannot get \"%s\": %ld\n", ftm_name, PTR_ERR(clk));
273 return PTR_ERR(clk);
274 }
275 err = clk_prepare_enable(clk);
276 if (err)
277 pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n",
278 ftm_name, err);
279
280 return clk_get_rate(clk);
281}
282
283static unsigned long __init ftm_clk_init(struct device_node *np)
284{
285 unsigned long freq;
286
287 freq = __ftm_clk_init(np, "ftm-evt-counter-en", "ftm-evt");
288 if (freq <= 0)
289 return 0;
290
291 freq = __ftm_clk_init(np, "ftm-src-counter-en", "ftm-src");
292 if (freq <= 0)
293 return 0;
294
295 return freq;
296}
297
298static int __init ftm_calc_closest_round_cyc(unsigned long freq)
299{
300 priv->ps = 0;
301
302 /* The counter register is only using the lower 16 bits, and
303 * if the 'freq' value is to big here, then the periodic_cyc
304 * may exceed 0xFFFF.
305 */
306 do {
307 priv->periodic_cyc = DIV_ROUND_CLOSEST(freq,
308 HZ * (1 << priv->ps++));
309 } while (priv->periodic_cyc > 0xFFFF);
310
311 if (priv->ps > FTM_PS_MAX) {
312 pr_err("ftm: the prescaler is %lu > %d\n",
313 priv->ps, FTM_PS_MAX);
314 return -EINVAL;
315 }
316
317 return 0;
318}
319
320static void __init ftm_timer_init(struct device_node *np)
321{
322 unsigned long freq;
323 int irq;
324
325 priv = kzalloc(sizeof(*priv), GFP_KERNEL);
326 if (!priv)
327 return;
328
329 priv->clkevt_base = of_iomap(np, 0);
330 if (!priv->clkevt_base) {
331 pr_err("ftm: unable to map event timer registers\n");
332 goto err;
333 }
334
335 priv->clksrc_base = of_iomap(np, 1);
336 if (!priv->clksrc_base) {
337 pr_err("ftm: unable to map source timer registers\n");
338 goto err;
339 }
340
341 irq = irq_of_parse_and_map(np, 0);
342 if (irq <= 0) {
343 pr_err("ftm: unable to get IRQ from DT, %d\n", irq);
344 goto err;
345 }
346
347 priv->big_endian = of_property_read_bool(np, "big-endian");
348
349 freq = ftm_clk_init(np);
350 if (!freq)
351 goto err;
352
353 if (ftm_calc_closest_round_cyc(freq))
354 goto err;
355
356 if (ftm_clocksource_init(freq))
357 goto err;
358
359 if (ftm_clockevent_init(freq, irq))
360 goto err;
361
362 return;
363
364err:
365 kfree(priv);
366}
367CLOCKSOURCE_OF_DECLARE(flextimer, "fsl,ftm-timer", ftm_timer_init);