aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/clocksource/timer-imx-tpm.c
diff options
context:
space:
mode:
authorDong Aisheng <aisheng.dong@nxp.com>2017-08-01 04:40:17 -0400
committerDaniel Lezcano <daniel.lezcano@linaro.org>2017-08-29 05:07:56 -0400
commit059ab7b82eecfc23bc58c491d72ee6b424163578 (patch)
treeadf2612e76cf6331fae6abb7ccab324808941a80 /drivers/clocksource/timer-imx-tpm.c
parent34b0c26cd5458a991626bc8424e029562dceed21 (diff)
clocksource/drivers/imx-tpm: Add imx tpm timer support
IMX Timer/PWM Module (TPM) supports both timer and pwm function while this patch only adds the timer support. PWM would be added later. The TPM counter, compare and capture registers are clocked by an asynchronous clock that can remain enabled in low power modes. NOTE: We observed in a very small probability, the bus fabric contention between GPU and A7 may results a few cycles delay of writing CNT registers which may cause the min_delta event got missed, so we need add a ETIME check here in case it happened. Cc: Daniel Lezcano <daniel.lezcano@linaro.org> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Shawn Guo <shawnguo@kernel.org> Cc: Anson Huang <Anson.Huang@nxp.com> Cc: Bai Ping <ping.bai@nxp.com> Signed-off-by: Dong Aisheng <aisheng.dong@nxp.com> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Diffstat (limited to 'drivers/clocksource/timer-imx-tpm.c')
-rw-r--r--drivers/clocksource/timer-imx-tpm.c239
1 files changed, 239 insertions, 0 deletions
diff --git a/drivers/clocksource/timer-imx-tpm.c b/drivers/clocksource/timer-imx-tpm.c
new file mode 100644
index 000000000000..21bffdcb2f20
--- /dev/null
+++ b/drivers/clocksource/timer-imx-tpm.c
@@ -0,0 +1,239 @@
1/*
2 * Copyright 2016 Freescale Semiconductor, Inc.
3 * Copyright 2017 NXP
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 */
10
11#include <linux/clk.h>
12#include <linux/clockchips.h>
13#include <linux/clocksource.h>
14#include <linux/delay.h>
15#include <linux/interrupt.h>
16#include <linux/of_address.h>
17#include <linux/of_irq.h>
18#include <linux/sched_clock.h>
19
20#define TPM_SC 0x10
21#define TPM_SC_CMOD_INC_PER_CNT (0x1 << 3)
22#define TPM_SC_CMOD_DIV_DEFAULT 0x3
23#define TPM_CNT 0x14
24#define TPM_MOD 0x18
25#define TPM_STATUS 0x1c
26#define TPM_STATUS_CH0F BIT(0)
27#define TPM_C0SC 0x20
28#define TPM_C0SC_CHIE BIT(6)
29#define TPM_C0SC_MODE_SHIFT 2
30#define TPM_C0SC_MODE_MASK 0x3c
31#define TPM_C0SC_MODE_SW_COMPARE 0x4
32#define TPM_C0V 0x24
33
34static void __iomem *timer_base;
35static struct clock_event_device clockevent_tpm;
36
37static inline void tpm_timer_disable(void)
38{
39 unsigned int val;
40
41 /* channel disable */
42 val = readl(timer_base + TPM_C0SC);
43 val &= ~(TPM_C0SC_MODE_MASK | TPM_C0SC_CHIE);
44 writel(val, timer_base + TPM_C0SC);
45}
46
47static inline void tpm_timer_enable(void)
48{
49 unsigned int val;
50
51 /* channel enabled in sw compare mode */
52 val = readl(timer_base + TPM_C0SC);
53 val |= (TPM_C0SC_MODE_SW_COMPARE << TPM_C0SC_MODE_SHIFT) |
54 TPM_C0SC_CHIE;
55 writel(val, timer_base + TPM_C0SC);
56}
57
58static inline void tpm_irq_acknowledge(void)
59{
60 writel(TPM_STATUS_CH0F, timer_base + TPM_STATUS);
61}
62
63static struct delay_timer tpm_delay_timer;
64
65static inline unsigned long tpm_read_counter(void)
66{
67 return readl(timer_base + TPM_CNT);
68}
69
70static unsigned long tpm_read_current_timer(void)
71{
72 return tpm_read_counter();
73}
74
75static u64 notrace tpm_read_sched_clock(void)
76{
77 return tpm_read_counter();
78}
79
80static int __init tpm_clocksource_init(unsigned long rate)
81{
82 tpm_delay_timer.read_current_timer = &tpm_read_current_timer;
83 tpm_delay_timer.freq = rate;
84 register_current_timer_delay(&tpm_delay_timer);
85
86 sched_clock_register(tpm_read_sched_clock, 32, rate);
87
88 return clocksource_mmio_init(timer_base + TPM_CNT, "imx-tpm",
89 rate, 200, 32, clocksource_mmio_readl_up);
90}
91
92static int tpm_set_next_event(unsigned long delta,
93 struct clock_event_device *evt)
94{
95 unsigned long next, now;
96
97 next = tpm_read_counter();
98 next += delta;
99 writel(next, timer_base + TPM_C0V);
100 now = tpm_read_counter();
101
102 /*
103 * NOTE: We observed in a very small probability, the bus fabric
104 * contention between GPU and A7 may results a few cycles delay
105 * of writing CNT registers which may cause the min_delta event got
106 * missed, so we need add a ETIME check here in case it happened.
107 */
108 return (int)((next - now) <= 0) ? -ETIME : 0;
109}
110
111static int tpm_set_state_oneshot(struct clock_event_device *evt)
112{
113 tpm_timer_enable();
114
115 return 0;
116}
117
118static int tpm_set_state_shutdown(struct clock_event_device *evt)
119{
120 tpm_timer_disable();
121
122 return 0;
123}
124
125static irqreturn_t tpm_timer_interrupt(int irq, void *dev_id)
126{
127 struct clock_event_device *evt = dev_id;
128
129 tpm_irq_acknowledge();
130
131 evt->event_handler(evt);
132
133 return IRQ_HANDLED;
134}
135
136static struct clock_event_device clockevent_tpm = {
137 .name = "i.MX7ULP TPM Timer",
138 .features = CLOCK_EVT_FEAT_ONESHOT,
139 .set_state_oneshot = tpm_set_state_oneshot,
140 .set_next_event = tpm_set_next_event,
141 .set_state_shutdown = tpm_set_state_shutdown,
142 .rating = 200,
143};
144
145static int __init tpm_clockevent_init(unsigned long rate, int irq)
146{
147 int ret;
148
149 ret = request_irq(irq, tpm_timer_interrupt, IRQF_TIMER | IRQF_IRQPOLL,
150 "i.MX7ULP TPM Timer", &clockevent_tpm);
151
152 clockevent_tpm.cpumask = cpumask_of(0);
153 clockevent_tpm.irq = irq;
154 clockevents_config_and_register(&clockevent_tpm,
155 rate, 300, 0xfffffffe);
156
157 return ret;
158}
159
160static int __init tpm_timer_init(struct device_node *np)
161{
162 struct clk *ipg, *per;
163 int irq, ret;
164 u32 rate;
165
166 timer_base = of_iomap(np, 0);
167 if (!timer_base) {
168 pr_err("tpm: failed to get base address\n");
169 return -ENXIO;
170 }
171
172 irq = irq_of_parse_and_map(np, 0);
173 if (!irq) {
174 pr_err("tpm: failed to get irq\n");
175 ret = -ENOENT;
176 goto err_iomap;
177 }
178
179 ipg = of_clk_get_by_name(np, "ipg");
180 per = of_clk_get_by_name(np, "per");
181 if (IS_ERR(ipg) || IS_ERR(per)) {
182 pr_err("tpm: failed to get igp or per clk\n");
183 ret = -ENODEV;
184 goto err_clk_get;
185 }
186
187 /* enable clk before accessing registers */
188 ret = clk_prepare_enable(ipg);
189 if (ret) {
190 pr_err("tpm: ipg clock enable failed (%d)\n", ret);
191 goto err_clk_get;
192 }
193
194 ret = clk_prepare_enable(per);
195 if (ret) {
196 pr_err("tpm: per clock enable failed (%d)\n", ret);
197 goto err_per_clk_enable;
198 }
199
200 /*
201 * Initialize tpm module to a known state
202 * 1) Counter disabled
203 * 2) TPM counter operates in up counting mode
204 * 3) Timer Overflow Interrupt disabled
205 * 4) Channel0 disabled
206 * 5) DMA transfers disabled
207 */
208 writel(0, timer_base + TPM_SC);
209 writel(0, timer_base + TPM_CNT);
210 writel(0, timer_base + TPM_C0SC);
211
212 /* increase per cnt, div 8 by default */
213 writel(TPM_SC_CMOD_INC_PER_CNT | TPM_SC_CMOD_DIV_DEFAULT,
214 timer_base + TPM_SC);
215
216 /* set MOD register to maximum for free running mode */
217 writel(0xffffffff, timer_base + TPM_MOD);
218
219 rate = clk_get_rate(per) >> 3;
220 ret = tpm_clocksource_init(rate);
221 if (ret)
222 goto err_per_clk_enable;
223
224 ret = tpm_clockevent_init(rate, irq);
225 if (ret)
226 goto err_per_clk_enable;
227
228 return 0;
229
230err_per_clk_enable:
231 clk_disable_unprepare(ipg);
232err_clk_get:
233 clk_put(per);
234 clk_put(ipg);
235err_iomap:
236 iounmap(timer_base);
237 return ret;
238}
239TIMER_OF_DECLARE(imx7ulp, "fsl,imx7ulp-tpm", tpm_timer_init);