Noam Camus | a532245 | 2015-10-17 22:37:30 +0300 | [diff] [blame] | 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 | |
Noam Camus | 0465fb4 | 2016-11-16 08:31:12 +0200 | [diff] [blame] | 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); |
Arnd Bergmann | a26b0d4 | 2016-11-22 15:33:44 +0100 | [diff] [blame] | 56 | ret = PTR_ERR_OR_ZERO(*clk); |
| 57 | if (ret) { |
Rafał Miłecki | ac9ce6d | 2017-03-09 10:47:10 +0100 | [diff] [blame] | 58 | pr_err("timer missing clk\n"); |
Arnd Bergmann | a26b0d4 | 2016-11-22 15:33:44 +0100 | [diff] [blame] | 59 | return ret; |
Noam Camus | 0465fb4 | 2016-11-16 08:31:12 +0200 | [diff] [blame] | 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 | } |
Noam Camus | a532245 | 2015-10-17 22:37:30 +0300 | [diff] [blame] | 79 | |
Thomas Gleixner | a5a1d1c | 2016-12-21 20:32:01 +0100 | [diff] [blame] | 80 | static u64 nps_clksrc_read(struct clocksource *clksrc) |
Noam Camus | a532245 | 2015-10-17 22:37:30 +0300 | [diff] [blame] | 81 | { |
| 82 | int cluster = raw_smp_processor_id() >> NPS_CLUSTER_OFFSET; |
| 83 | |
Thomas Gleixner | a5a1d1c | 2016-12-21 20:32:01 +0100 | [diff] [blame] | 84 | return (u64)ioread32be(nps_msu_reg_low_addr[cluster]); |
Noam Camus | a532245 | 2015-10-17 22:37:30 +0300 | [diff] [blame] | 85 | } |
| 86 | |
Noam Camus | 0465fb4 | 2016-11-16 08:31:12 +0200 | [diff] [blame] | 87 | static int __init nps_setup_clocksource(struct device_node *node) |
Noam Camus | a532245 | 2015-10-17 22:37:30 +0300 | [diff] [blame] | 88 | { |
| 89 | int ret, cluster; |
Noam Camus | 0465fb4 | 2016-11-16 08:31:12 +0200 | [diff] [blame] | 90 | struct clk *clk; |
| 91 | unsigned long nps_timer1_freq; |
| 92 | |
Noam Camus | a532245 | 2015-10-17 22:37:30 +0300 | [diff] [blame] | 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), |
Noam Camus | 0465fb4 | 2016-11-16 08:31:12 +0200 | [diff] [blame] | 97 | NPS_MSU_BLKID, NPS_MSU_TICK_LOW); |
Noam Camus | a532245 | 2015-10-17 22:37:30 +0300 | [diff] [blame] | 98 | |
Noam Camus | 0465fb4 | 2016-11-16 08:31:12 +0200 | [diff] [blame] | 99 | ret = nps_get_timer_clk(node, &nps_timer1_freq, &clk); |
| 100 | if (ret) |
Daniel Lezcano | 2d9b6506 | 2016-06-15 14:16:11 +0200 | [diff] [blame] | 101 | return ret; |
Noam Camus | a532245 | 2015-10-17 22:37:30 +0300 | [diff] [blame] | 102 | |
Noam Camus | 0465fb4 | 2016-11-16 08:31:12 +0200 | [diff] [blame] | 103 | ret = clocksource_mmio_init(nps_msu_reg_low_addr, "nps-tick", |
| 104 | nps_timer1_freq, 300, 32, nps_clksrc_read); |
Noam Camus | a532245 | 2015-10-17 22:37:30 +0300 | [diff] [blame] | 105 | if (ret) { |
| 106 | pr_err("Couldn't register clock source.\n"); |
| 107 | clk_disable_unprepare(clk); |
| 108 | } |
Daniel Lezcano | 2d9b6506 | 2016-06-15 14:16:11 +0200 | [diff] [blame] | 109 | |
| 110 | return ret; |
Noam Camus | a532245 | 2015-10-17 22:37:30 +0300 | [diff] [blame] | 111 | } |
| 112 | |
Daniel Lezcano | 1727339 | 2017-05-26 16:56:11 +0200 | [diff] [blame] | 113 | TIMER_OF_DECLARE(ezchip_nps400_clksrc, "ezchip,nps400-timer", |
Noam Camus | 0465fb4 | 2016-11-16 08:31:12 +0200 | [diff] [blame] | 114 | nps_setup_clocksource); |
Daniel Lezcano | 1727339 | 2017-05-26 16:56:11 +0200 | [diff] [blame] | 115 | TIMER_OF_DECLARE(ezchip_nps400_clk_src, "ezchip,nps400-timer1", |
Noam Camus | 60263dc | 2016-11-17 09:12:43 +0200 | [diff] [blame] | 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) { |
Rafał Miłecki | ac9ce6d | 2017-03-09 10:47:10 +0100 | [diff] [blame] | 250 | pr_err("clockevent: missing irq\n"); |
Noam Camus | 60263dc | 2016-11-17 09:12:43 +0200 | [diff] [blame] | 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) { |
Rafał Miłecki | ac9ce6d | 2017-03-09 10:47:10 +0100 | [diff] [blame] | 273 | pr_err("Failed to setup hotplug state\n"); |
Noam Camus | 60263dc | 2016-11-17 09:12:43 +0200 | [diff] [blame] | 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 | |
Daniel Lezcano | 1727339 | 2017-05-26 16:56:11 +0200 | [diff] [blame] | 282 | TIMER_OF_DECLARE(ezchip_nps400_clk_evt, "ezchip,nps400-timer0", |
Noam Camus | 60263dc | 2016-11-17 09:12:43 +0200 | [diff] [blame] | 283 | nps_setup_clockevent); |
| 284 | #endif /* CONFIG_EZNPS_MTM_EXT */ |