]>
Commit | Line | Data |
---|---|---|
a5322457 NC |
1 | /* |
2 | * Copyright (c) 2016, Mellanox Technologies. All rights reserved. | |
3 | * | |
4 | * This software is available to you under a choice of one of two | |
5 | * licenses. You may choose to be licensed under the terms of the GNU | |
6 | * General Public License (GPL) Version 2, available from the file | |
7 | * COPYING in the main directory of this source tree, or the | |
8 | * OpenIB.org BSD license below: | |
9 | * | |
10 | * Redistribution and use in source and binary forms, with or | |
11 | * without modification, are permitted provided that the following | |
12 | * conditions are met: | |
13 | * | |
14 | * - Redistributions of source code must retain the above | |
15 | * copyright notice, this list of conditions and the following | |
16 | * disclaimer. | |
17 | * | |
18 | * - Redistributions in binary form must reproduce the above | |
19 | * copyright notice, this list of conditions and the following | |
20 | * disclaimer in the documentation and/or other materials | |
21 | * provided with the distribution. | |
22 | * | |
23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
24 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
25 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | |
26 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS | |
27 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN | |
28 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN | |
29 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
30 | * SOFTWARE. | |
31 | */ | |
32 | ||
33 | #include <linux/interrupt.h> | |
34 | #include <linux/clocksource.h> | |
35 | #include <linux/clockchips.h> | |
36 | #include <linux/clk.h> | |
37 | #include <linux/of.h> | |
38 | #include <linux/of_irq.h> | |
39 | #include <linux/cpu.h> | |
40 | #include <soc/nps/common.h> | |
41 | ||
42 | #define NPS_MSU_TICK_LOW 0xC8 | |
43 | #define NPS_CLUSTER_OFFSET 8 | |
44 | #define NPS_CLUSTER_NUM 16 | |
45 | ||
46 | /* This array is per cluster of CPUs (Each NPS400 cluster got 256 CPUs) */ | |
47 | static void *nps_msu_reg_low_addr[NPS_CLUSTER_NUM] __read_mostly; | |
48 | ||
0465fb49 NC |
49 | static int __init nps_get_timer_clk(struct device_node *node, |
50 | unsigned long *timer_freq, | |
51 | struct clk **clk) | |
52 | { | |
53 | int ret; | |
54 | ||
55 | *clk = of_clk_get(node, 0); | |
a26b0d49 AB |
56 | ret = PTR_ERR_OR_ZERO(*clk); |
57 | if (ret) { | |
ac9ce6d1 | 58 | pr_err("timer missing clk\n"); |
a26b0d49 | 59 | return ret; |
0465fb49 NC |
60 | } |
61 | ||
62 | ret = clk_prepare_enable(*clk); | |
63 | if (ret) { | |
64 | pr_err("Couldn't enable parent clk\n"); | |
65 | clk_put(*clk); | |
66 | return ret; | |
67 | } | |
68 | ||
69 | *timer_freq = clk_get_rate(*clk); | |
70 | if (!(*timer_freq)) { | |
71 | pr_err("Couldn't get clk rate\n"); | |
72 | clk_disable_unprepare(*clk); | |
73 | clk_put(*clk); | |
74 | return -EINVAL; | |
75 | } | |
76 | ||
77 | return 0; | |
78 | } | |
a5322457 | 79 | |
a5a1d1c2 | 80 | static u64 nps_clksrc_read(struct clocksource *clksrc) |
a5322457 NC |
81 | { |
82 | int cluster = raw_smp_processor_id() >> NPS_CLUSTER_OFFSET; | |
83 | ||
a5a1d1c2 | 84 | return (u64)ioread32be(nps_msu_reg_low_addr[cluster]); |
a5322457 NC |
85 | } |
86 | ||
0465fb49 | 87 | static int __init nps_setup_clocksource(struct device_node *node) |
a5322457 NC |
88 | { |
89 | int ret, cluster; | |
0465fb49 NC |
90 | struct clk *clk; |
91 | unsigned long nps_timer1_freq; | |
92 | ||
a5322457 NC |
93 | |
94 | for (cluster = 0; cluster < NPS_CLUSTER_NUM; cluster++) | |
95 | nps_msu_reg_low_addr[cluster] = | |
96 | nps_host_reg((cluster << NPS_CLUSTER_OFFSET), | |
0465fb49 | 97 | NPS_MSU_BLKID, NPS_MSU_TICK_LOW); |
a5322457 | 98 | |
0465fb49 NC |
99 | ret = nps_get_timer_clk(node, &nps_timer1_freq, &clk); |
100 | if (ret) | |
2d9b6506 | 101 | return ret; |
a5322457 | 102 | |
0465fb49 NC |
103 | ret = clocksource_mmio_init(nps_msu_reg_low_addr, "nps-tick", |
104 | nps_timer1_freq, 300, 32, nps_clksrc_read); | |
a5322457 NC |
105 | if (ret) { |
106 | pr_err("Couldn't register clock source.\n"); | |
107 | clk_disable_unprepare(clk); | |
108 | } | |
2d9b6506 DL |
109 | |
110 | return ret; | |
a5322457 NC |
111 | } |
112 | ||
17273395 | 113 | TIMER_OF_DECLARE(ezchip_nps400_clksrc, "ezchip,nps400-timer", |
0465fb49 | 114 | nps_setup_clocksource); |
17273395 | 115 | TIMER_OF_DECLARE(ezchip_nps400_clk_src, "ezchip,nps400-timer1", |
60263dcd NC |
116 | nps_setup_clocksource); |
117 | ||
118 | #ifdef CONFIG_EZNPS_MTM_EXT | |
119 | #include <soc/nps/mtm.h> | |
120 | ||
121 | /* Timer related Aux registers */ | |
122 | #define NPS_REG_TIMER0_TSI 0xFFFFF850 | |
123 | #define NPS_REG_TIMER0_LIMIT 0x23 | |
124 | #define NPS_REG_TIMER0_CTRL 0x22 | |
125 | #define NPS_REG_TIMER0_CNT 0x21 | |
126 | ||
127 | /* | |
128 | * Interrupt Enabled (IE) - re-arm the timer | |
129 | * Not Halted (NH) - is cleared when working with JTAG (for debug) | |
130 | */ | |
131 | #define TIMER0_CTRL_IE BIT(0) | |
132 | #define TIMER0_CTRL_NH BIT(1) | |
133 | ||
134 | static unsigned long nps_timer0_freq; | |
135 | static unsigned long nps_timer0_irq; | |
136 | ||
137 | static void nps_clkevent_rm_thread(void) | |
138 | { | |
139 | int thread; | |
140 | unsigned int cflags, enabled_threads; | |
141 | ||
142 | hw_schd_save(&cflags); | |
143 | ||
144 | enabled_threads = read_aux_reg(NPS_REG_TIMER0_TSI); | |
145 | ||
146 | /* remove thread from TSI1 */ | |
147 | thread = read_aux_reg(CTOP_AUX_THREAD_ID); | |
148 | enabled_threads &= ~(1 << thread); | |
149 | write_aux_reg(NPS_REG_TIMER0_TSI, enabled_threads); | |
150 | ||
151 | /* Acknowledge and if needed re-arm the timer */ | |
152 | if (!enabled_threads) | |
153 | write_aux_reg(NPS_REG_TIMER0_CTRL, TIMER0_CTRL_NH); | |
154 | else | |
155 | write_aux_reg(NPS_REG_TIMER0_CTRL, | |
156 | TIMER0_CTRL_IE | TIMER0_CTRL_NH); | |
157 | ||
158 | hw_schd_restore(cflags); | |
159 | } | |
160 | ||
161 | static void nps_clkevent_add_thread(unsigned long delta) | |
162 | { | |
163 | int thread; | |
164 | unsigned int cflags, enabled_threads; | |
165 | ||
166 | hw_schd_save(&cflags); | |
167 | ||
168 | /* add thread to TSI1 */ | |
169 | thread = read_aux_reg(CTOP_AUX_THREAD_ID); | |
170 | enabled_threads = read_aux_reg(NPS_REG_TIMER0_TSI); | |
171 | enabled_threads |= (1 << thread); | |
172 | write_aux_reg(NPS_REG_TIMER0_TSI, enabled_threads); | |
173 | ||
174 | /* set next timer event */ | |
175 | write_aux_reg(NPS_REG_TIMER0_LIMIT, delta); | |
176 | write_aux_reg(NPS_REG_TIMER0_CNT, 0); | |
177 | write_aux_reg(NPS_REG_TIMER0_CTRL, | |
178 | TIMER0_CTRL_IE | TIMER0_CTRL_NH); | |
179 | ||
180 | hw_schd_restore(cflags); | |
181 | } | |
182 | ||
183 | /* | |
184 | * Whenever anyone tries to change modes, we just mask interrupts | |
185 | * and wait for the next event to get set. | |
186 | */ | |
187 | static int nps_clkevent_set_state(struct clock_event_device *dev) | |
188 | { | |
189 | nps_clkevent_rm_thread(); | |
190 | disable_percpu_irq(nps_timer0_irq); | |
191 | ||
192 | return 0; | |
193 | } | |
194 | ||
195 | static int nps_clkevent_set_next_event(unsigned long delta, | |
196 | struct clock_event_device *dev) | |
197 | { | |
198 | nps_clkevent_add_thread(delta); | |
199 | enable_percpu_irq(nps_timer0_irq, IRQ_TYPE_NONE); | |
200 | ||
201 | return 0; | |
202 | } | |
203 | ||
204 | static DEFINE_PER_CPU(struct clock_event_device, nps_clockevent_device) = { | |
205 | .name = "NPS Timer0", | |
206 | .features = CLOCK_EVT_FEAT_ONESHOT, | |
207 | .rating = 300, | |
208 | .set_next_event = nps_clkevent_set_next_event, | |
209 | .set_state_oneshot = nps_clkevent_set_state, | |
210 | .set_state_oneshot_stopped = nps_clkevent_set_state, | |
211 | .set_state_shutdown = nps_clkevent_set_state, | |
212 | .tick_resume = nps_clkevent_set_state, | |
213 | }; | |
214 | ||
215 | static irqreturn_t timer_irq_handler(int irq, void *dev_id) | |
216 | { | |
217 | struct clock_event_device *evt = dev_id; | |
218 | ||
219 | nps_clkevent_rm_thread(); | |
220 | evt->event_handler(evt); | |
221 | ||
222 | return IRQ_HANDLED; | |
223 | } | |
224 | ||
225 | static int nps_timer_starting_cpu(unsigned int cpu) | |
226 | { | |
227 | struct clock_event_device *evt = this_cpu_ptr(&nps_clockevent_device); | |
228 | ||
229 | evt->cpumask = cpumask_of(smp_processor_id()); | |
230 | ||
231 | clockevents_config_and_register(evt, nps_timer0_freq, 0, ULONG_MAX); | |
232 | enable_percpu_irq(nps_timer0_irq, IRQ_TYPE_NONE); | |
233 | ||
234 | return 0; | |
235 | } | |
236 | ||
237 | static int nps_timer_dying_cpu(unsigned int cpu) | |
238 | { | |
239 | disable_percpu_irq(nps_timer0_irq); | |
240 | return 0; | |
241 | } | |
242 | ||
243 | static int __init nps_setup_clockevent(struct device_node *node) | |
244 | { | |
245 | struct clk *clk; | |
246 | int ret; | |
247 | ||
248 | nps_timer0_irq = irq_of_parse_and_map(node, 0); | |
249 | if (nps_timer0_irq <= 0) { | |
ac9ce6d1 | 250 | pr_err("clockevent: missing irq\n"); |
60263dcd NC |
251 | return -EINVAL; |
252 | } | |
253 | ||
254 | ret = nps_get_timer_clk(node, &nps_timer0_freq, &clk); | |
255 | if (ret) | |
256 | return ret; | |
257 | ||
258 | /* Needs apriori irq_set_percpu_devid() done in intc map function */ | |
259 | ret = request_percpu_irq(nps_timer0_irq, timer_irq_handler, | |
260 | "Timer0 (per-cpu-tick)", | |
261 | &nps_clockevent_device); | |
262 | if (ret) { | |
263 | pr_err("Couldn't request irq\n"); | |
264 | clk_disable_unprepare(clk); | |
265 | return ret; | |
266 | } | |
267 | ||
268 | ret = cpuhp_setup_state(CPUHP_AP_ARC_TIMER_STARTING, | |
269 | "clockevents/nps:starting", | |
270 | nps_timer_starting_cpu, | |
271 | nps_timer_dying_cpu); | |
272 | if (ret) { | |
ac9ce6d1 | 273 | pr_err("Failed to setup hotplug state\n"); |
60263dcd NC |
274 | clk_disable_unprepare(clk); |
275 | free_percpu_irq(nps_timer0_irq, &nps_clockevent_device); | |
276 | return ret; | |
277 | } | |
278 | ||
279 | return 0; | |
280 | } | |
281 | ||
17273395 | 282 | TIMER_OF_DECLARE(ezchip_nps400_clk_evt, "ezchip,nps400-timer0", |
60263dcd NC |
283 | nps_setup_clockevent); |
284 | #endif /* CONFIG_EZNPS_MTM_EXT */ |