Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2013-2014 Altera Corporation |
| 3 | * Copyright (C) 2010 Tobias Klauser <tklauser@distanz.ch> |
| 4 | * Copyright (C) 2004 Microtronix Datacom Ltd. |
| 5 | * |
| 6 | * This file is subject to the terms and conditions of the GNU General Public |
| 7 | * License. See the file "COPYING" in the main directory of this archive |
| 8 | * for more details. |
| 9 | */ |
| 10 | |
Herbert Xu | 05dee9c | 2015-06-09 12:46:46 +0800 | [diff] [blame] | 11 | #include <linux/export.h> |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 12 | #include <linux/interrupt.h> |
| 13 | #include <linux/clockchips.h> |
| 14 | #include <linux/clocksource.h> |
| 15 | #include <linux/delay.h> |
| 16 | #include <linux/of.h> |
| 17 | #include <linux/of_address.h> |
| 18 | #include <linux/of_irq.h> |
| 19 | #include <linux/io.h> |
| 20 | #include <linux/slab.h> |
| 21 | |
Ley Foon Tan | a8955cc | 2015-06-24 17:52:44 +0800 | [diff] [blame] | 22 | #define ALTR_TIMER_COMPATIBLE "altr,timer-1.0" |
| 23 | |
| 24 | #define ALTERA_TIMER_STATUS_REG 0 |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 25 | #define ALTERA_TIMER_CONTROL_REG 4 |
| 26 | #define ALTERA_TIMER_PERIODL_REG 8 |
| 27 | #define ALTERA_TIMER_PERIODH_REG 12 |
| 28 | #define ALTERA_TIMER_SNAPL_REG 16 |
| 29 | #define ALTERA_TIMER_SNAPH_REG 20 |
| 30 | |
| 31 | #define ALTERA_TIMER_CONTROL_ITO_MSK (0x1) |
| 32 | #define ALTERA_TIMER_CONTROL_CONT_MSK (0x2) |
| 33 | #define ALTERA_TIMER_CONTROL_START_MSK (0x4) |
| 34 | #define ALTERA_TIMER_CONTROL_STOP_MSK (0x8) |
| 35 | |
| 36 | struct nios2_timer { |
| 37 | void __iomem *base; |
| 38 | unsigned long freq; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 39 | }; |
| 40 | |
| 41 | struct nios2_clockevent_dev { |
| 42 | struct nios2_timer timer; |
| 43 | struct clock_event_device ced; |
| 44 | }; |
| 45 | |
| 46 | struct nios2_clocksource { |
| 47 | struct nios2_timer timer; |
| 48 | struct clocksource cs; |
| 49 | }; |
| 50 | |
| 51 | static inline struct nios2_clockevent_dev * |
| 52 | to_nios2_clkevent(struct clock_event_device *evt) |
| 53 | { |
| 54 | return container_of(evt, struct nios2_clockevent_dev, ced); |
| 55 | } |
| 56 | |
| 57 | static inline struct nios2_clocksource * |
| 58 | to_nios2_clksource(struct clocksource *cs) |
| 59 | { |
| 60 | return container_of(cs, struct nios2_clocksource, cs); |
| 61 | } |
| 62 | |
| 63 | static u16 timer_readw(struct nios2_timer *timer, u32 offs) |
| 64 | { |
| 65 | return readw(timer->base + offs); |
| 66 | } |
| 67 | |
| 68 | static void timer_writew(struct nios2_timer *timer, u16 val, u32 offs) |
| 69 | { |
| 70 | writew(val, timer->base + offs); |
| 71 | } |
| 72 | |
| 73 | static inline unsigned long read_timersnapshot(struct nios2_timer *timer) |
| 74 | { |
| 75 | unsigned long count; |
| 76 | |
| 77 | timer_writew(timer, 0, ALTERA_TIMER_SNAPL_REG); |
| 78 | count = timer_readw(timer, ALTERA_TIMER_SNAPH_REG) << 16 | |
| 79 | timer_readw(timer, ALTERA_TIMER_SNAPL_REG); |
| 80 | |
| 81 | return count; |
| 82 | } |
| 83 | |
| 84 | static cycle_t nios2_timer_read(struct clocksource *cs) |
| 85 | { |
| 86 | struct nios2_clocksource *nios2_cs = to_nios2_clksource(cs); |
| 87 | unsigned long flags; |
| 88 | u32 count; |
| 89 | |
| 90 | local_irq_save(flags); |
| 91 | count = read_timersnapshot(&nios2_cs->timer); |
| 92 | local_irq_restore(flags); |
| 93 | |
| 94 | /* Counter is counting down */ |
| 95 | return ~count; |
| 96 | } |
| 97 | |
| 98 | static struct nios2_clocksource nios2_cs = { |
| 99 | .cs = { |
| 100 | .name = "nios2-clksrc", |
| 101 | .rating = 250, |
| 102 | .read = nios2_timer_read, |
| 103 | .mask = CLOCKSOURCE_MASK(32), |
| 104 | .flags = CLOCK_SOURCE_IS_CONTINUOUS, |
| 105 | }, |
| 106 | }; |
| 107 | |
| 108 | cycles_t get_cycles(void) |
| 109 | { |
| 110 | return nios2_timer_read(&nios2_cs.cs); |
| 111 | } |
Herbert Xu | 05dee9c | 2015-06-09 12:46:46 +0800 | [diff] [blame] | 112 | EXPORT_SYMBOL(get_cycles); |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 113 | |
| 114 | static void nios2_timer_start(struct nios2_timer *timer) |
| 115 | { |
| 116 | u16 ctrl; |
| 117 | |
| 118 | ctrl = timer_readw(timer, ALTERA_TIMER_CONTROL_REG); |
| 119 | ctrl |= ALTERA_TIMER_CONTROL_START_MSK; |
| 120 | timer_writew(timer, ctrl, ALTERA_TIMER_CONTROL_REG); |
| 121 | } |
| 122 | |
| 123 | static void nios2_timer_stop(struct nios2_timer *timer) |
| 124 | { |
| 125 | u16 ctrl; |
| 126 | |
| 127 | ctrl = timer_readw(timer, ALTERA_TIMER_CONTROL_REG); |
| 128 | ctrl |= ALTERA_TIMER_CONTROL_STOP_MSK; |
| 129 | timer_writew(timer, ctrl, ALTERA_TIMER_CONTROL_REG); |
| 130 | } |
| 131 | |
| 132 | static void nios2_timer_config(struct nios2_timer *timer, unsigned long period, |
Viresh Kumar | 549a14c1 | 2015-08-18 13:59:28 +0800 | [diff] [blame] | 133 | bool periodic) |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 134 | { |
| 135 | u16 ctrl; |
| 136 | |
| 137 | /* The timer's actual period is one cycle greater than the value |
| 138 | * stored in the period register. */ |
| 139 | period--; |
| 140 | |
| 141 | ctrl = timer_readw(timer, ALTERA_TIMER_CONTROL_REG); |
| 142 | /* stop counter */ |
| 143 | timer_writew(timer, ctrl | ALTERA_TIMER_CONTROL_STOP_MSK, |
| 144 | ALTERA_TIMER_CONTROL_REG); |
| 145 | |
| 146 | /* write new count */ |
| 147 | timer_writew(timer, period, ALTERA_TIMER_PERIODL_REG); |
| 148 | timer_writew(timer, period >> 16, ALTERA_TIMER_PERIODH_REG); |
| 149 | |
| 150 | ctrl |= ALTERA_TIMER_CONTROL_START_MSK | ALTERA_TIMER_CONTROL_ITO_MSK; |
Viresh Kumar | 549a14c1 | 2015-08-18 13:59:28 +0800 | [diff] [blame] | 151 | if (periodic) |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 152 | ctrl |= ALTERA_TIMER_CONTROL_CONT_MSK; |
| 153 | else |
| 154 | ctrl &= ~ALTERA_TIMER_CONTROL_CONT_MSK; |
| 155 | timer_writew(timer, ctrl, ALTERA_TIMER_CONTROL_REG); |
| 156 | } |
| 157 | |
| 158 | static int nios2_timer_set_next_event(unsigned long delta, |
| 159 | struct clock_event_device *evt) |
| 160 | { |
| 161 | struct nios2_clockevent_dev *nios2_ced = to_nios2_clkevent(evt); |
| 162 | |
Viresh Kumar | 549a14c1 | 2015-08-18 13:59:28 +0800 | [diff] [blame] | 163 | nios2_timer_config(&nios2_ced->timer, delta, false); |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 164 | |
| 165 | return 0; |
| 166 | } |
| 167 | |
Viresh Kumar | 549a14c1 | 2015-08-18 13:59:28 +0800 | [diff] [blame] | 168 | static int nios2_timer_shutdown(struct clock_event_device *evt) |
| 169 | { |
| 170 | struct nios2_clockevent_dev *nios2_ced = to_nios2_clkevent(evt); |
| 171 | struct nios2_timer *timer = &nios2_ced->timer; |
| 172 | |
| 173 | nios2_timer_stop(timer); |
| 174 | return 0; |
| 175 | } |
| 176 | |
| 177 | static int nios2_timer_set_periodic(struct clock_event_device *evt) |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 178 | { |
| 179 | unsigned long period; |
| 180 | struct nios2_clockevent_dev *nios2_ced = to_nios2_clkevent(evt); |
| 181 | struct nios2_timer *timer = &nios2_ced->timer; |
| 182 | |
Viresh Kumar | 549a14c1 | 2015-08-18 13:59:28 +0800 | [diff] [blame] | 183 | period = DIV_ROUND_UP(timer->freq, HZ); |
| 184 | nios2_timer_config(timer, period, true); |
| 185 | return 0; |
| 186 | } |
| 187 | |
| 188 | static int nios2_timer_resume(struct clock_event_device *evt) |
| 189 | { |
| 190 | struct nios2_clockevent_dev *nios2_ced = to_nios2_clkevent(evt); |
| 191 | struct nios2_timer *timer = &nios2_ced->timer; |
| 192 | |
| 193 | nios2_timer_start(timer); |
| 194 | return 0; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 195 | } |
| 196 | |
| 197 | irqreturn_t timer_interrupt(int irq, void *dev_id) |
| 198 | { |
| 199 | struct clock_event_device *evt = (struct clock_event_device *) dev_id; |
| 200 | struct nios2_clockevent_dev *nios2_ced = to_nios2_clkevent(evt); |
| 201 | |
| 202 | /* Clear the interrupt condition */ |
| 203 | timer_writew(&nios2_ced->timer, 0, ALTERA_TIMER_STATUS_REG); |
| 204 | evt->event_handler(evt); |
| 205 | |
| 206 | return IRQ_HANDLED; |
| 207 | } |
| 208 | |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 209 | static int __init nios2_timer_get_base_and_freq(struct device_node *np, |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 210 | void __iomem **base, u32 *freq) |
| 211 | { |
| 212 | *base = of_iomap(np, 0); |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 213 | if (!*base) { |
| 214 | pr_crit("Unable to map reg for %s\n", np->name); |
| 215 | return -ENXIO; |
| 216 | } |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 217 | |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 218 | if (of_property_read_u32(np, "clock-frequency", freq)) { |
| 219 | pr_crit("Unable to get %s clock frequency\n", np->name); |
| 220 | return -EINVAL; |
| 221 | } |
| 222 | |
| 223 | return 0; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 224 | } |
| 225 | |
| 226 | static struct nios2_clockevent_dev nios2_ce = { |
| 227 | .ced = { |
| 228 | .name = "nios2-clkevent", |
| 229 | .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, |
| 230 | .rating = 250, |
| 231 | .shift = 32, |
| 232 | .set_next_event = nios2_timer_set_next_event, |
Viresh Kumar | 549a14c1 | 2015-08-18 13:59:28 +0800 | [diff] [blame] | 233 | .set_state_shutdown = nios2_timer_shutdown, |
| 234 | .set_state_periodic = nios2_timer_set_periodic, |
| 235 | .set_state_oneshot = nios2_timer_shutdown, |
| 236 | .tick_resume = nios2_timer_resume, |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 237 | }, |
| 238 | }; |
| 239 | |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 240 | static __init int nios2_clockevent_init(struct device_node *timer) |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 241 | { |
| 242 | void __iomem *iobase; |
| 243 | u32 freq; |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 244 | int irq, ret; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 245 | |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 246 | ret = nios2_timer_get_base_and_freq(timer, &iobase, &freq); |
| 247 | if (ret) |
| 248 | return ret; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 249 | |
| 250 | irq = irq_of_parse_and_map(timer, 0); |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 251 | if (!irq) { |
| 252 | pr_crit("Unable to parse timer irq\n"); |
| 253 | return -EINVAL; |
| 254 | } |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 255 | |
| 256 | nios2_ce.timer.base = iobase; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 257 | nios2_ce.timer.freq = freq; |
| 258 | |
| 259 | nios2_ce.ced.cpumask = cpumask_of(0); |
| 260 | nios2_ce.ced.irq = irq; |
| 261 | |
| 262 | nios2_timer_stop(&nios2_ce.timer); |
| 263 | /* clear pending interrupt */ |
| 264 | timer_writew(&nios2_ce.timer, 0, ALTERA_TIMER_STATUS_REG); |
| 265 | |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 266 | ret = request_irq(irq, timer_interrupt, IRQF_TIMER, timer->name, |
| 267 | &nios2_ce.ced); |
| 268 | if (ret) { |
| 269 | pr_crit("Unable to setup timer irq\n"); |
| 270 | return ret; |
| 271 | } |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 272 | |
| 273 | clockevents_config_and_register(&nios2_ce.ced, freq, 1, ULONG_MAX); |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 274 | |
| 275 | return 0; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 276 | } |
| 277 | |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 278 | static __init int nios2_clocksource_init(struct device_node *timer) |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 279 | { |
| 280 | unsigned int ctrl; |
| 281 | void __iomem *iobase; |
| 282 | u32 freq; |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 283 | int ret; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 284 | |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 285 | ret = nios2_timer_get_base_and_freq(timer, &iobase, &freq); |
| 286 | if (ret) |
| 287 | return ret; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 288 | |
| 289 | nios2_cs.timer.base = iobase; |
| 290 | nios2_cs.timer.freq = freq; |
| 291 | |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 292 | ret = clocksource_register_hz(&nios2_cs.cs, freq); |
| 293 | if (ret) |
| 294 | return ret; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 295 | |
| 296 | timer_writew(&nios2_cs.timer, USHRT_MAX, ALTERA_TIMER_PERIODL_REG); |
| 297 | timer_writew(&nios2_cs.timer, USHRT_MAX, ALTERA_TIMER_PERIODH_REG); |
| 298 | |
| 299 | /* interrupt disable + continuous + start */ |
| 300 | ctrl = ALTERA_TIMER_CONTROL_CONT_MSK | ALTERA_TIMER_CONTROL_START_MSK; |
| 301 | timer_writew(&nios2_cs.timer, ctrl, ALTERA_TIMER_CONTROL_REG); |
| 302 | |
| 303 | /* Calibrate the delay loop directly */ |
| 304 | lpj_fine = freq / HZ; |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 305 | |
| 306 | return 0; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 307 | } |
| 308 | |
| 309 | /* |
| 310 | * The first timer instance will use as a clockevent. If there are two or |
| 311 | * more instances, the second one gets used as clocksource and all |
| 312 | * others are unused. |
| 313 | */ |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 314 | static int __init nios2_time_init(struct device_node *timer) |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 315 | { |
| 316 | static int num_called; |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 317 | int ret; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 318 | |
| 319 | switch (num_called) { |
| 320 | case 0: |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 321 | ret = nios2_clockevent_init(timer); |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 322 | break; |
| 323 | case 1: |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 324 | ret = nios2_clocksource_init(timer); |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 325 | break; |
| 326 | default: |
| 327 | break; |
| 328 | } |
| 329 | |
| 330 | num_called++; |
Daniel Lezcano | dd1364a | 2016-06-07 00:04:02 +0200 | [diff] [blame] | 331 | |
| 332 | return ret; |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 333 | } |
| 334 | |
| 335 | void read_persistent_clock(struct timespec *ts) |
| 336 | { |
| 337 | ts->tv_sec = mktime(2007, 1, 1, 0, 0, 0); |
| 338 | ts->tv_nsec = 0; |
| 339 | } |
| 340 | |
| 341 | void __init time_init(void) |
| 342 | { |
Ley Foon Tan | a8955cc | 2015-06-24 17:52:44 +0800 | [diff] [blame] | 343 | struct device_node *np; |
| 344 | int count = 0; |
| 345 | |
| 346 | for_each_compatible_node(np, NULL, ALTR_TIMER_COMPATIBLE) |
| 347 | count++; |
| 348 | |
| 349 | if (count < 2) |
| 350 | panic("%d timer is found, it needs 2 timers in system\n", count); |
| 351 | |
Marc Zyngier | 3722ed2 | 2015-09-28 15:49:18 +0100 | [diff] [blame] | 352 | clocksource_probe(); |
Ley Foon Tan | 4182de9 | 2014-11-06 15:20:04 +0800 | [diff] [blame] | 353 | } |
| 354 | |
Daniel Lezcano | 177cf6e | 2016-06-07 00:27:44 +0200 | [diff] [blame^] | 355 | CLOCKSOURCE_OF_DECLARE(nios2_timer, ALTR_TIMER_COMPATIBLE, nios2_time_init); |