diff options
author | Maxime Ripard <maxime.ripard@free-electrons.com> | 2013-11-07 06:01:48 -0500 |
---|---|---|
committer | Daniel Lezcano <daniel.lezcano@linaro.org> | 2013-12-11 05:37:50 -0500 |
commit | 67905540e8b8eaf51e621cfd2ef15641d6d5b9a7 (patch) | |
tree | e5f34ff5d62c075201fcd2337383cbac66183c50 /drivers/clocksource/timer-sun5i.c | |
parent | 5df9affb50a09e0cb571c4fa3e2d577db85c7475 (diff) |
clocksource: Add Allwinner SoCs HS timers driver
Most of the Allwinner SoCs (at this time, all but the A10) also have a
High Speed timers that are not using the 24MHz oscillator as a source
but rather the AHB clock running much faster.
The IP is slightly different between the A10s/A13 and the one used in
the A20/A31, since the latter have 4 timers available, while the former
have only 2 of them.
[dlezcano] : Fixed conflict with b788beda "Order Kconfig options
alphabetically"
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Tested-by: Emilio López <emilio@elopez.com.ar>
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Diffstat (limited to 'drivers/clocksource/timer-sun5i.c')
-rw-r--r-- | drivers/clocksource/timer-sun5i.c | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/drivers/clocksource/timer-sun5i.c b/drivers/clocksource/timer-sun5i.c new file mode 100644 index 000000000000..bddc52233d2a --- /dev/null +++ b/drivers/clocksource/timer-sun5i.c | |||
@@ -0,0 +1,192 @@ | |||
1 | /* | ||
2 | * Allwinner SoCs hstimer driver. | ||
3 | * | ||
4 | * Copyright (C) 2013 Maxime Ripard | ||
5 | * | ||
6 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
7 | * | ||
8 | * This file is licensed under the terms of the GNU General Public | ||
9 | * License version 2. This program is licensed "as is" without any | ||
10 | * warranty of any kind, whether express or implied. | ||
11 | */ | ||
12 | |||
13 | #include <linux/clk.h> | ||
14 | #include <linux/clockchips.h> | ||
15 | #include <linux/delay.h> | ||
16 | #include <linux/interrupt.h> | ||
17 | #include <linux/irq.h> | ||
18 | #include <linux/irqreturn.h> | ||
19 | #include <linux/sched_clock.h> | ||
20 | #include <linux/of.h> | ||
21 | #include <linux/of_address.h> | ||
22 | #include <linux/of_irq.h> | ||
23 | |||
24 | #define TIMER_IRQ_EN_REG 0x00 | ||
25 | #define TIMER_IRQ_EN(val) BIT(val) | ||
26 | #define TIMER_IRQ_ST_REG 0x04 | ||
27 | #define TIMER_CTL_REG(val) (0x20 * (val) + 0x10) | ||
28 | #define TIMER_CTL_ENABLE BIT(0) | ||
29 | #define TIMER_CTL_RELOAD BIT(1) | ||
30 | #define TIMER_CTL_CLK_PRES(val) (((val) & 0x7) << 4) | ||
31 | #define TIMER_CTL_ONESHOT BIT(7) | ||
32 | #define TIMER_INTVAL_LO_REG(val) (0x20 * (val) + 0x14) | ||
33 | #define TIMER_INTVAL_HI_REG(val) (0x20 * (val) + 0x18) | ||
34 | #define TIMER_CNTVAL_LO_REG(val) (0x20 * (val) + 0x1c) | ||
35 | #define TIMER_CNTVAL_HI_REG(val) (0x20 * (val) + 0x20) | ||
36 | |||
37 | #define TIMER_SYNC_TICKS 3 | ||
38 | |||
39 | static void __iomem *timer_base; | ||
40 | static u32 ticks_per_jiffy; | ||
41 | |||
42 | /* | ||
43 | * When we disable a timer, we need to wait at least for 2 cycles of | ||
44 | * the timer source clock. We will use for that the clocksource timer | ||
45 | * that is already setup and runs at the same frequency than the other | ||
46 | * timers, and we never will be disabled. | ||
47 | */ | ||
48 | static void sun5i_clkevt_sync(void) | ||
49 | { | ||
50 | u32 old = readl(timer_base + TIMER_CNTVAL_LO_REG(1)); | ||
51 | |||
52 | while ((old - readl(timer_base + TIMER_CNTVAL_LO_REG(1))) < TIMER_SYNC_TICKS) | ||
53 | cpu_relax(); | ||
54 | } | ||
55 | |||
56 | static void sun5i_clkevt_time_stop(u8 timer) | ||
57 | { | ||
58 | u32 val = readl(timer_base + TIMER_CTL_REG(timer)); | ||
59 | writel(val & ~TIMER_CTL_ENABLE, timer_base + TIMER_CTL_REG(timer)); | ||
60 | |||
61 | sun5i_clkevt_sync(); | ||
62 | } | ||
63 | |||
64 | static void sun5i_clkevt_time_setup(u8 timer, u32 delay) | ||
65 | { | ||
66 | writel(delay, timer_base + TIMER_INTVAL_LO_REG(timer)); | ||
67 | } | ||
68 | |||
69 | static void sun5i_clkevt_time_start(u8 timer, bool periodic) | ||
70 | { | ||
71 | u32 val = readl(timer_base + TIMER_CTL_REG(timer)); | ||
72 | |||
73 | if (periodic) | ||
74 | val &= ~TIMER_CTL_ONESHOT; | ||
75 | else | ||
76 | val |= TIMER_CTL_ONESHOT; | ||
77 | |||
78 | writel(val | TIMER_CTL_ENABLE | TIMER_CTL_RELOAD, | ||
79 | timer_base + TIMER_CTL_REG(timer)); | ||
80 | } | ||
81 | |||
82 | static void sun5i_clkevt_mode(enum clock_event_mode mode, | ||
83 | struct clock_event_device *clk) | ||
84 | { | ||
85 | switch (mode) { | ||
86 | case CLOCK_EVT_MODE_PERIODIC: | ||
87 | sun5i_clkevt_time_stop(0); | ||
88 | sun5i_clkevt_time_setup(0, ticks_per_jiffy); | ||
89 | sun5i_clkevt_time_start(0, true); | ||
90 | break; | ||
91 | case CLOCK_EVT_MODE_ONESHOT: | ||
92 | sun5i_clkevt_time_stop(0); | ||
93 | sun5i_clkevt_time_start(0, false); | ||
94 | break; | ||
95 | case CLOCK_EVT_MODE_UNUSED: | ||
96 | case CLOCK_EVT_MODE_SHUTDOWN: | ||
97 | default: | ||
98 | sun5i_clkevt_time_stop(0); | ||
99 | break; | ||
100 | } | ||
101 | } | ||
102 | |||
103 | static int sun5i_clkevt_next_event(unsigned long evt, | ||
104 | struct clock_event_device *unused) | ||
105 | { | ||
106 | sun5i_clkevt_time_stop(0); | ||
107 | sun5i_clkevt_time_setup(0, evt - TIMER_SYNC_TICKS); | ||
108 | sun5i_clkevt_time_start(0, false); | ||
109 | |||
110 | return 0; | ||
111 | } | ||
112 | |||
113 | static struct clock_event_device sun5i_clockevent = { | ||
114 | .name = "sun5i_tick", | ||
115 | .rating = 340, | ||
116 | .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, | ||
117 | .set_mode = sun5i_clkevt_mode, | ||
118 | .set_next_event = sun5i_clkevt_next_event, | ||
119 | }; | ||
120 | |||
121 | |||
122 | static irqreturn_t sun5i_timer_interrupt(int irq, void *dev_id) | ||
123 | { | ||
124 | struct clock_event_device *evt = (struct clock_event_device *)dev_id; | ||
125 | |||
126 | writel(0x1, timer_base + TIMER_IRQ_ST_REG); | ||
127 | evt->event_handler(evt); | ||
128 | |||
129 | return IRQ_HANDLED; | ||
130 | } | ||
131 | |||
132 | static struct irqaction sun5i_timer_irq = { | ||
133 | .name = "sun5i_timer0", | ||
134 | .flags = IRQF_TIMER | IRQF_IRQPOLL, | ||
135 | .handler = sun5i_timer_interrupt, | ||
136 | .dev_id = &sun5i_clockevent, | ||
137 | }; | ||
138 | |||
139 | static u32 sun5i_timer_sched_read(void) | ||
140 | { | ||
141 | return ~readl(timer_base + TIMER_CNTVAL_LO_REG(1)); | ||
142 | } | ||
143 | |||
144 | static void __init sun5i_timer_init(struct device_node *node) | ||
145 | { | ||
146 | unsigned long rate; | ||
147 | struct clk *clk; | ||
148 | int ret, irq; | ||
149 | u32 val; | ||
150 | |||
151 | timer_base = of_iomap(node, 0); | ||
152 | if (!timer_base) | ||
153 | panic("Can't map registers"); | ||
154 | |||
155 | irq = irq_of_parse_and_map(node, 0); | ||
156 | if (irq <= 0) | ||
157 | panic("Can't parse IRQ"); | ||
158 | |||
159 | clk = of_clk_get(node, 0); | ||
160 | if (IS_ERR(clk)) | ||
161 | panic("Can't get timer clock"); | ||
162 | clk_prepare_enable(clk); | ||
163 | rate = clk_get_rate(clk); | ||
164 | |||
165 | writel(~0, timer_base + TIMER_INTVAL_LO_REG(1)); | ||
166 | writel(TIMER_CTL_ENABLE | TIMER_CTL_RELOAD, | ||
167 | timer_base + TIMER_CTL_REG(1)); | ||
168 | |||
169 | setup_sched_clock(sun5i_timer_sched_read, 32, rate); | ||
170 | clocksource_mmio_init(timer_base + TIMER_CNTVAL_LO_REG(1), node->name, | ||
171 | rate, 340, 32, clocksource_mmio_readl_down); | ||
172 | |||
173 | ticks_per_jiffy = DIV_ROUND_UP(rate, HZ); | ||
174 | |||
175 | ret = setup_irq(irq, &sun5i_timer_irq); | ||
176 | if (ret) | ||
177 | pr_warn("failed to setup irq %d\n", irq); | ||
178 | |||
179 | /* Enable timer0 interrupt */ | ||
180 | val = readl(timer_base + TIMER_IRQ_EN_REG); | ||
181 | writel(val | TIMER_IRQ_EN(0), timer_base + TIMER_IRQ_EN_REG); | ||
182 | |||
183 | sun5i_clockevent.cpumask = cpu_possible_mask; | ||
184 | sun5i_clockevent.irq = irq; | ||
185 | |||
186 | clockevents_config_and_register(&sun5i_clockevent, rate, | ||
187 | TIMER_SYNC_TICKS, 0xffffffff); | ||
188 | } | ||
189 | CLOCKSOURCE_OF_DECLARE(sun5i_a13, "allwinner,sun5i-a13-hstimer", | ||
190 | sun5i_timer_init); | ||
191 | CLOCKSOURCE_OF_DECLARE(sun7i_a20, "allwinner,sun7i-a20-hstimer", | ||
192 | sun5i_timer_init); | ||