summaryrefslogtreecommitdiff
path: root/drivers/clocksource/timer-rockchip.c
blob: 1f95d0aca08f2984cd8f2014e913c41018b71321 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Rockchip timer support
 *
 * Copyright (C) Daniel Lezcano <daniel.lezcano@linaro.org>
 */
#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/sched_clock.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>

#define TIMER_NAME "rk_timer"

#define TIMER_LOAD_COUNT0	0x00
#define TIMER_LOAD_COUNT1	0x04
#define TIMER_CURRENT_VALUE0	0x08
#define TIMER_CURRENT_VALUE1	0x0C
#define TIMER_CONTROL_REG3288	0x10
#define TIMER_CONTROL_REG3399	0x1c
#define TIMER_INT_STATUS	0x18

#define TIMER_DISABLE		0x0
#define TIMER_ENABLE		0x1
#define TIMER_MODE_FREE_RUNNING			(0 << 1)
#define TIMER_MODE_USER_DEFINED_COUNT		(1 << 1)
#define TIMER_INT_UNMASK			(1 << 2)

struct rk_timer {
	void __iomem *base;
	void __iomem *ctrl;
	struct clk *clk;
	struct clk *pclk;
	u32 freq;
	int irq;
};

struct rk_clkevt {
	struct clock_event_device ce;
	struct rk_timer timer;
};

static struct rk_clkevt *rk_clkevt;
static struct rk_timer *rk_clksrc;

static inline struct rk_timer *rk_timer(struct clock_event_device *ce)
{
	return &container_of(ce, struct rk_clkevt, ce)->timer;
}

static inline void rk_timer_disable(struct rk_timer *timer)
{
	writel_relaxed(TIMER_DISABLE, timer->ctrl);
}

static inline void rk_timer_enable(struct rk_timer *timer, u32 flags)
{
	writel_relaxed(TIMER_ENABLE | flags, timer->ctrl);
}

static void rk_timer_update_counter(unsigned long cycles,
				    struct rk_timer *timer)
{
	writel_relaxed(cycles, timer->base + TIMER_LOAD_COUNT0);
	writel_relaxed(0, timer->base + TIMER_LOAD_COUNT1);
}

static void rk_timer_interrupt_clear(struct rk_timer *timer)
{
	writel_relaxed(1, timer->base + TIMER_INT_STATUS);
}

static inline int rk_timer_set_next_event(unsigned long cycles,
					  struct clock_event_device *ce)
{
	struct rk_timer *timer = rk_timer(ce);

	rk_timer_disable(timer);
	rk_timer_update_counter(cycles, timer);
	rk_timer_enable(timer, TIMER_MODE_USER_DEFINED_COUNT |
			       TIMER_INT_UNMASK);
	return 0;
}

static int rk_timer_shutdown(struct clock_event_device *ce)
{
	struct rk_timer *timer = rk_timer(ce);

	rk_timer_disable(timer);
	return 0;
}

static int rk_timer_set_periodic(struct clock_event_device *ce)
{
	struct rk_timer *timer = rk_timer(ce);

	rk_timer_disable(timer);
	rk_timer_update_counter(timer->freq / HZ - 1, timer);
	rk_timer_enable(timer, TIMER_MODE_FREE_RUNNING | TIMER_INT_UNMASK);
	return 0;
}

static irqreturn_t rk_timer_interrupt(int irq, void *dev_id)
{
	struct clock_event_device *ce = dev_id;
	struct rk_timer *timer = rk_timer(ce);

	rk_timer_interrupt_clear(timer);

	if (clockevent_state_oneshot(ce))
		rk_timer_disable(timer);

	ce->event_handler(ce);

	return IRQ_HANDLED;
}

static u64 notrace rk_timer_sched_read(void)
{
	return ~readl_relaxed(rk_clksrc->base + TIMER_CURRENT_VALUE0);
}

static int __init
rk_timer_probe(struct rk_timer *timer, struct device_node *np)
{
	struct clk *timer_clk;
	struct clk *pclk;
	int ret = -EINVAL, irq;
	u32 ctrl_reg = TIMER_CONTROL_REG3288;

	timer->base = of_iomap(np, 0);
	if (!timer->base) {
		pr_err("Failed to get base address for '%s'\n", TIMER_NAME);
		return -ENXIO;
	}

	if (of_device_is_compatible(np, "rockchip,rk3399-timer"))
		ctrl_reg = TIMER_CONTROL_REG3399;

	timer->ctrl = timer->base + ctrl_reg;

	pclk = of_clk_get_by_name(np, "pclk");
	if (IS_ERR(pclk)) {
		ret = PTR_ERR(pclk);
		pr_err("Failed to get pclk for '%s'\n", TIMER_NAME);
		goto out_unmap;
	}

	ret = clk_prepare_enable(pclk);
	if (ret) {
		pr_err("Failed to enable pclk for '%s'\n", TIMER_NAME);
		goto out_unmap;
	}
	timer->pclk = pclk;

	timer_clk = of_clk_get_by_name(np, "timer");
	if (IS_ERR(timer_clk)) {
		ret = PTR_ERR(timer_clk);
		pr_err("Failed to get timer clock for '%s'\n", TIMER_NAME);
		goto out_timer_clk;
	}

	ret = clk_prepare_enable(timer_clk);
	if (ret) {
		pr_err("Failed to enable timer clock\n");
		goto out_timer_clk;
	}
	timer->clk = timer_clk;

	timer->freq = clk_get_rate(timer_clk);

	irq = irq_of_parse_and_map(np, 0);
	if (!irq) {
		ret = -EINVAL;
		pr_err("Failed to map interrupts for '%s'\n", TIMER_NAME);
		goto out_irq;
	}
	timer->irq = irq;

	rk_timer_interrupt_clear(timer);
	rk_timer_disable(timer);
	return 0;

out_irq:
	clk_disable_unprepare(timer_clk);
out_timer_clk:
	clk_disable_unprepare(pclk);
out_unmap:
	iounmap(timer->base);

	return ret;
}

static void __init rk_timer_cleanup(struct rk_timer *timer)
{
	clk_disable_unprepare(timer->clk);
	clk_disable_unprepare(timer->pclk);
	iounmap(timer->base);
}

static int __init rk_clkevt_init(struct device_node *np)
{
	struct clock_event_device *ce;
	int ret = -EINVAL;

	rk_clkevt = kzalloc(sizeof(struct rk_clkevt), GFP_KERNEL);
	if (!rk_clkevt) {
		ret = -ENOMEM;
		goto out;
	}

	ret = rk_timer_probe(&rk_clkevt->timer, np);
	if (ret)
		goto out_probe;

	ce = &rk_clkevt->ce;
	ce->name = TIMER_NAME;
	ce->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT |
		       CLOCK_EVT_FEAT_DYNIRQ;
	ce->set_next_event = rk_timer_set_next_event;
	ce->set_state_shutdown = rk_timer_shutdown;
	ce->set_state_periodic = rk_timer_set_periodic;
	ce->irq = rk_clkevt->timer.irq;
	ce->cpumask = cpu_possible_mask;
	ce->rating = 250;

	ret = request_irq(rk_clkevt->timer.irq, rk_timer_interrupt, IRQF_TIMER,
			  TIMER_NAME, ce);
	if (ret) {
		pr_err("Failed to initialize '%s': %d\n",
			TIMER_NAME, ret);
		goto out_irq;
	}

	clockevents_config_and_register(&rk_clkevt->ce,
					rk_clkevt->timer.freq, 1, UINT_MAX);
	return 0;

out_irq:
	rk_timer_cleanup(&rk_clkevt->timer);
out_probe:
	kfree(rk_clkevt);
out:
	/* Leave rk_clkevt not NULL to prevent future init */
	rk_clkevt = ERR_PTR(ret);
	return ret;
}

static int __init rk_clksrc_init(struct device_node *np)
{
	int ret = -EINVAL;

	rk_clksrc = kzalloc(sizeof(struct rk_timer), GFP_KERNEL);
	if (!rk_clksrc) {
		ret = -ENOMEM;
		goto out;
	}

	ret = rk_timer_probe(rk_clksrc, np);
	if (ret)
		goto out_probe;

	rk_timer_update_counter(UINT_MAX, rk_clksrc);
	rk_timer_enable(rk_clksrc, 0);

	ret = clocksource_mmio_init(rk_clksrc->base + TIMER_CURRENT_VALUE0,
		TIMER_NAME, rk_clksrc->freq, 250, 32,
		clocksource_mmio_readl_down);
	if (ret) {
		pr_err("Failed to register clocksource\n");
		goto out_clocksource;
	}

	sched_clock_register(rk_timer_sched_read, 32, rk_clksrc->freq);
	return 0;

out_clocksource:
	rk_timer_cleanup(rk_clksrc);
out_probe:
	kfree(rk_clksrc);
out:
	/* Leave rk_clksrc not NULL to prevent future init */
	rk_clksrc = ERR_PTR(ret);
	return ret;
}

static int __init rk_timer_init(struct device_node *np)
{
	if (!rk_clkevt)
		return rk_clkevt_init(np);

	if (!rk_clksrc)
		return rk_clksrc_init(np);

	pr_err("Too many timer definitions for '%s'\n", TIMER_NAME);
	return -EINVAL;
}

TIMER_OF_DECLARE(rk3288_timer, "rockchip,rk3288-timer", rk_timer_init);
TIMER_OF_DECLARE(rk3399_timer, "rockchip,rk3399-timer", rk_timer_init);