diff options
Diffstat (limited to 'drivers/clocksource/timer-sun4i.c')
-rw-r--r-- | drivers/clocksource/timer-sun4i.c | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/drivers/clocksource/timer-sun4i.c b/drivers/clocksource/timer-sun4i.c new file mode 100644 index 000000000000..6e0180aaf784 --- /dev/null +++ b/drivers/clocksource/timer-sun4i.c | |||
@@ -0,0 +1,220 @@ | |||
1 | /* | ||
2 | * Allwinner A1X SoCs timer handling. | ||
3 | * | ||
4 | * Copyright (C) 2012 Maxime Ripard | ||
5 | * | ||
6 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
7 | * | ||
8 | * Based on code from | ||
9 | * Allwinner Technology Co., Ltd. <www.allwinnertech.com> | ||
10 | * Benn Huang <benn@allwinnertech.com> | ||
11 | * | ||
12 | * This file is licensed under the terms of the GNU General Public | ||
13 | * License version 2. This program is licensed "as is" without any | ||
14 | * warranty of any kind, whether express or implied. | ||
15 | */ | ||
16 | |||
17 | #include <linux/clk.h> | ||
18 | #include <linux/clockchips.h> | ||
19 | #include <linux/interrupt.h> | ||
20 | #include <linux/irq.h> | ||
21 | #include <linux/irqreturn.h> | ||
22 | #include <linux/sched_clock.h> | ||
23 | #include <linux/of.h> | ||
24 | #include <linux/of_address.h> | ||
25 | #include <linux/of_irq.h> | ||
26 | |||
27 | #include "timer-of.h" | ||
28 | |||
29 | #define TIMER_IRQ_EN_REG 0x00 | ||
30 | #define TIMER_IRQ_EN(val) BIT(val) | ||
31 | #define TIMER_IRQ_ST_REG 0x04 | ||
32 | #define TIMER_CTL_REG(val) (0x10 * val + 0x10) | ||
33 | #define TIMER_CTL_ENABLE BIT(0) | ||
34 | #define TIMER_CTL_RELOAD BIT(1) | ||
35 | #define TIMER_CTL_CLK_SRC(val) (((val) & 0x3) << 2) | ||
36 | #define TIMER_CTL_CLK_SRC_OSC24M (1) | ||
37 | #define TIMER_CTL_CLK_PRES(val) (((val) & 0x7) << 4) | ||
38 | #define TIMER_CTL_ONESHOT BIT(7) | ||
39 | #define TIMER_INTVAL_REG(val) (0x10 * (val) + 0x14) | ||
40 | #define TIMER_CNTVAL_REG(val) (0x10 * (val) + 0x18) | ||
41 | |||
42 | #define TIMER_SYNC_TICKS 3 | ||
43 | |||
44 | /* | ||
45 | * When we disable a timer, we need to wait at least for 2 cycles of | ||
46 | * the timer source clock. We will use for that the clocksource timer | ||
47 | * that is already setup and runs at the same frequency than the other | ||
48 | * timers, and we never will be disabled. | ||
49 | */ | ||
50 | static void sun4i_clkevt_sync(void __iomem *base) | ||
51 | { | ||
52 | u32 old = readl(base + TIMER_CNTVAL_REG(1)); | ||
53 | |||
54 | while ((old - readl(base + TIMER_CNTVAL_REG(1))) < TIMER_SYNC_TICKS) | ||
55 | cpu_relax(); | ||
56 | } | ||
57 | |||
58 | static void sun4i_clkevt_time_stop(void __iomem *base, u8 timer) | ||
59 | { | ||
60 | u32 val = readl(base + TIMER_CTL_REG(timer)); | ||
61 | writel(val & ~TIMER_CTL_ENABLE, base + TIMER_CTL_REG(timer)); | ||
62 | sun4i_clkevt_sync(base); | ||
63 | } | ||
64 | |||
65 | static void sun4i_clkevt_time_setup(void __iomem *base, u8 timer, | ||
66 | unsigned long delay) | ||
67 | { | ||
68 | writel(delay, base + TIMER_INTVAL_REG(timer)); | ||
69 | } | ||
70 | |||
71 | static void sun4i_clkevt_time_start(void __iomem *base, u8 timer, | ||
72 | bool periodic) | ||
73 | { | ||
74 | u32 val = readl(base + TIMER_CTL_REG(timer)); | ||
75 | |||
76 | if (periodic) | ||
77 | val &= ~TIMER_CTL_ONESHOT; | ||
78 | else | ||
79 | val |= TIMER_CTL_ONESHOT; | ||
80 | |||
81 | writel(val | TIMER_CTL_ENABLE | TIMER_CTL_RELOAD, | ||
82 | base + TIMER_CTL_REG(timer)); | ||
83 | } | ||
84 | |||
85 | static int sun4i_clkevt_shutdown(struct clock_event_device *evt) | ||
86 | { | ||
87 | struct timer_of *to = to_timer_of(evt); | ||
88 | |||
89 | sun4i_clkevt_time_stop(timer_of_base(to), 0); | ||
90 | |||
91 | return 0; | ||
92 | } | ||
93 | |||
94 | static int sun4i_clkevt_set_oneshot(struct clock_event_device *evt) | ||
95 | { | ||
96 | struct timer_of *to = to_timer_of(evt); | ||
97 | |||
98 | sun4i_clkevt_time_stop(timer_of_base(to), 0); | ||
99 | sun4i_clkevt_time_start(timer_of_base(to), 0, false); | ||
100 | |||
101 | return 0; | ||
102 | } | ||
103 | |||
104 | static int sun4i_clkevt_set_periodic(struct clock_event_device *evt) | ||
105 | { | ||
106 | struct timer_of *to = to_timer_of(evt); | ||
107 | |||
108 | sun4i_clkevt_time_stop(timer_of_base(to), 0); | ||
109 | sun4i_clkevt_time_setup(timer_of_base(to), 0, timer_of_period(to)); | ||
110 | sun4i_clkevt_time_start(timer_of_base(to), 0, true); | ||
111 | |||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | static int sun4i_clkevt_next_event(unsigned long evt, | ||
116 | struct clock_event_device *clkevt) | ||
117 | { | ||
118 | struct timer_of *to = to_timer_of(clkevt); | ||
119 | |||
120 | sun4i_clkevt_time_stop(timer_of_base(to), 0); | ||
121 | sun4i_clkevt_time_setup(timer_of_base(to), 0, evt - TIMER_SYNC_TICKS); | ||
122 | sun4i_clkevt_time_start(timer_of_base(to), 0, false); | ||
123 | |||
124 | return 0; | ||
125 | } | ||
126 | |||
127 | static void sun4i_timer_clear_interrupt(void __iomem *base) | ||
128 | { | ||
129 | writel(TIMER_IRQ_EN(0), base + TIMER_IRQ_ST_REG); | ||
130 | } | ||
131 | |||
132 | static irqreturn_t sun4i_timer_interrupt(int irq, void *dev_id) | ||
133 | { | ||
134 | struct clock_event_device *evt = (struct clock_event_device *)dev_id; | ||
135 | struct timer_of *to = to_timer_of(evt); | ||
136 | |||
137 | sun4i_timer_clear_interrupt(timer_of_base(to)); | ||
138 | evt->event_handler(evt); | ||
139 | |||
140 | return IRQ_HANDLED; | ||
141 | } | ||
142 | |||
143 | static struct timer_of to = { | ||
144 | .flags = TIMER_OF_IRQ | TIMER_OF_CLOCK | TIMER_OF_BASE, | ||
145 | |||
146 | .clkevt = { | ||
147 | .name = "sun4i_tick", | ||
148 | .rating = 350, | ||
149 | .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, | ||
150 | .set_state_shutdown = sun4i_clkevt_shutdown, | ||
151 | .set_state_periodic = sun4i_clkevt_set_periodic, | ||
152 | .set_state_oneshot = sun4i_clkevt_set_oneshot, | ||
153 | .tick_resume = sun4i_clkevt_shutdown, | ||
154 | .set_next_event = sun4i_clkevt_next_event, | ||
155 | .cpumask = cpu_possible_mask, | ||
156 | }, | ||
157 | |||
158 | .of_irq = { | ||
159 | .handler = sun4i_timer_interrupt, | ||
160 | .flags = IRQF_TIMER | IRQF_IRQPOLL, | ||
161 | }, | ||
162 | }; | ||
163 | |||
164 | static u64 notrace sun4i_timer_sched_read(void) | ||
165 | { | ||
166 | return ~readl(timer_of_base(&to) + TIMER_CNTVAL_REG(1)); | ||
167 | } | ||
168 | |||
169 | static int __init sun4i_timer_init(struct device_node *node) | ||
170 | { | ||
171 | int ret; | ||
172 | u32 val; | ||
173 | |||
174 | ret = timer_of_init(node, &to); | ||
175 | if (ret) | ||
176 | return ret; | ||
177 | |||
178 | writel(~0, timer_of_base(&to) + TIMER_INTVAL_REG(1)); | ||
179 | writel(TIMER_CTL_ENABLE | TIMER_CTL_RELOAD | | ||
180 | TIMER_CTL_CLK_SRC(TIMER_CTL_CLK_SRC_OSC24M), | ||
181 | timer_of_base(&to) + TIMER_CTL_REG(1)); | ||
182 | |||
183 | /* | ||
184 | * sched_clock_register does not have priorities, and on sun6i and | ||
185 | * later there is a better sched_clock registered by arm_arch_timer.c | ||
186 | */ | ||
187 | if (of_machine_is_compatible("allwinner,sun4i-a10") || | ||
188 | of_machine_is_compatible("allwinner,sun5i-a13") || | ||
189 | of_machine_is_compatible("allwinner,sun5i-a10s")) | ||
190 | sched_clock_register(sun4i_timer_sched_read, 32, | ||
191 | timer_of_rate(&to)); | ||
192 | |||
193 | ret = clocksource_mmio_init(timer_of_base(&to) + TIMER_CNTVAL_REG(1), | ||
194 | node->name, timer_of_rate(&to), 350, 32, | ||
195 | clocksource_mmio_readl_down); | ||
196 | if (ret) { | ||
197 | pr_err("Failed to register clocksource\n"); | ||
198 | return ret; | ||
199 | } | ||
200 | |||
201 | writel(TIMER_CTL_CLK_SRC(TIMER_CTL_CLK_SRC_OSC24M), | ||
202 | timer_of_base(&to) + TIMER_CTL_REG(0)); | ||
203 | |||
204 | /* Make sure timer is stopped before playing with interrupts */ | ||
205 | sun4i_clkevt_time_stop(timer_of_base(&to), 0); | ||
206 | |||
207 | /* clear timer0 interrupt */ | ||
208 | sun4i_timer_clear_interrupt(timer_of_base(&to)); | ||
209 | |||
210 | clockevents_config_and_register(&to.clkevt, timer_of_rate(&to), | ||
211 | TIMER_SYNC_TICKS, 0xffffffff); | ||
212 | |||
213 | /* Enable timer0 interrupt */ | ||
214 | val = readl(timer_of_base(&to) + TIMER_IRQ_EN_REG); | ||
215 | writel(val | TIMER_IRQ_EN(0), timer_of_base(&to) + TIMER_IRQ_EN_REG); | ||
216 | |||
217 | return ret; | ||
218 | } | ||
219 | TIMER_OF_DECLARE(sun4i, "allwinner,sun4i-a10-timer", | ||
220 | sun4i_timer_init); | ||