summaryrefslogtreecommitdiffstats
path: root/drivers/clocksource/rockchip_timer.c
blob: d3c1742ded1af7655c3e2e77031ab3801ea84761 (plain)
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
/*
 * Rockchip timer support
 *
 * Copyright (C) Daniel Lezcano <daniel.lezcano@linaro.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/init.h>
#include <linux/interrupt.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_CONTROL_REG 0x10
#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 bc_timer {
	struct clock_event_device ce;
	void __iomem *base;
	u32 freq;
};

static struct bc_timer bc_timer;

static inline struct bc_timer *rk_timer(struct clock_event_device *ce)
{
	return container_of(ce, struct bc_timer, ce);
}

static inline void __iomem *rk_base(struct clock_event_device *ce)
{
	return rk_timer(ce)->base;
}

static inline void rk_timer_disable(struct clock_event_device *ce)
{
	writel_relaxed(TIMER_DISABLE, rk_base(ce) + TIMER_CONTROL_REG);
	dsb();
}

static inline void rk_timer_enable(struct clock_event_device *ce, u32 flags)
{
	writel_relaxed(TIMER_ENABLE | TIMER_INT_UNMASK | flags,
		       rk_base(ce) + TIMER_CONTROL_REG);
	dsb();
}

static void rk_timer_update_counter(unsigned long cycles,
				    struct clock_event_device *ce)
{
	writel_relaxed(cycles, rk_base(ce) + TIMER_LOAD_COUNT0);
	writel_relaxed(0, rk_base(ce) + TIMER_LOAD_COUNT1);
	dsb();
}

static void rk_timer_interrupt_clear(struct clock_event_device *ce)
{
	writel_relaxed(1, rk_base(ce) + TIMER_INT_STATUS);
	dsb();
}

static inline int rk_timer_set_next_event(unsigned long cycles,
					  struct clock_event_device *ce)
{
	rk_timer_disable(ce);
	rk_timer_update_counter(cycles, ce);
	rk_timer_enable(ce, TIMER_MODE_USER_DEFINED_COUNT);
	return 0;
}

static int rk_timer_shutdown(struct clock_event_device *ce)
{
	rk_timer_disable(ce);
	return 0;
}

static int rk_timer_set_periodic(struct clock_event_device *ce)
{
	rk_timer_disable(ce);
	rk_timer_update_counter(rk_timer(ce)->freq / HZ - 1, ce);
	rk_timer_enable(ce, TIMER_MODE_FREE_RUNNING);
	return 0;
}

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

	rk_timer_interrupt_clear(ce);

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

	ce->event_handler(ce);

	return IRQ_HANDLED;
}

static void __init rk_timer_init(struct device_node *np)
{
	struct clock_event_device *ce = &bc_timer.ce;
	struct clk *timer_clk;
	struct clk *pclk;
	int ret, irq;

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

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

	if (clk_prepare_enable(pclk)) {
		pr_err("Failed to enable pclk for '%s'\n", TIMER_NAME);
		return;
	}

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

	if (clk_prepare_enable(timer_clk)) {
		pr_err("Failed to enable timer clock\n");
		return;
	}

	bc_timer.freq = clk_get_rate(timer_clk);

	irq = irq_of_parse_and_map(np, 0);
	if (!irq) {
		pr_err("Failed to map interrupts for '%s'\n", TIMER_NAME);
		return;
	}

	ce->name = TIMER_NAME;
	ce->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
	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 = irq;
	ce->cpumask = cpumask_of(0);
	ce->rating = 250;

	rk_timer_interrupt_clear(ce);
	rk_timer_disable(ce);

	ret = request_irq(irq, rk_timer_interrupt, IRQF_TIMER, TIMER_NAME, ce);
	if (ret) {
		pr_err("Failed to initialize '%s': %d\n", TIMER_NAME, ret);
		return;
	}

	clockevents_config_and_register(ce, bc_timer.freq, 1, UINT_MAX);
}
CLOCKSOURCE_OF_DECLARE(rk_timer, "rockchip,rk3288-timer", rk_timer_init);
OpenPOWER on IntegriCloud