timer-fsl-ftm.c (7914B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Freescale FlexTimer Module (FTM) timer driver. 4 * 5 * Copyright 2014 Freescale Semiconductor, Inc. 6 */ 7 8#include <linux/clk.h> 9#include <linux/clockchips.h> 10#include <linux/clocksource.h> 11#include <linux/err.h> 12#include <linux/interrupt.h> 13#include <linux/io.h> 14#include <linux/of_address.h> 15#include <linux/of_irq.h> 16#include <linux/sched_clock.h> 17#include <linux/slab.h> 18#include <linux/fsl/ftm.h> 19 20#define FTM_SC_CLK(c) ((c) << FTM_SC_CLK_MASK_SHIFT) 21 22struct ftm_clock_device { 23 void __iomem *clksrc_base; 24 void __iomem *clkevt_base; 25 unsigned long periodic_cyc; 26 unsigned long ps; 27 bool big_endian; 28}; 29 30static struct ftm_clock_device *priv; 31 32static inline u32 ftm_readl(void __iomem *addr) 33{ 34 if (priv->big_endian) 35 return ioread32be(addr); 36 else 37 return ioread32(addr); 38} 39 40static inline void ftm_writel(u32 val, void __iomem *addr) 41{ 42 if (priv->big_endian) 43 iowrite32be(val, addr); 44 else 45 iowrite32(val, addr); 46} 47 48static inline void ftm_counter_enable(void __iomem *base) 49{ 50 u32 val; 51 52 /* select and enable counter clock source */ 53 val = ftm_readl(base + FTM_SC); 54 val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK); 55 val |= priv->ps | FTM_SC_CLK(1); 56 ftm_writel(val, base + FTM_SC); 57} 58 59static inline void ftm_counter_disable(void __iomem *base) 60{ 61 u32 val; 62 63 /* disable counter clock source */ 64 val = ftm_readl(base + FTM_SC); 65 val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK); 66 ftm_writel(val, base + FTM_SC); 67} 68 69static inline void ftm_irq_acknowledge(void __iomem *base) 70{ 71 u32 val; 72 73 val = ftm_readl(base + FTM_SC); 74 val &= ~FTM_SC_TOF; 75 ftm_writel(val, base + FTM_SC); 76} 77 78static inline void ftm_irq_enable(void __iomem *base) 79{ 80 u32 val; 81 82 val = ftm_readl(base + FTM_SC); 83 val |= FTM_SC_TOIE; 84 ftm_writel(val, base + FTM_SC); 85} 86 87static inline void ftm_irq_disable(void __iomem *base) 88{ 89 u32 val; 90 91 val = ftm_readl(base + FTM_SC); 92 val &= ~FTM_SC_TOIE; 93 ftm_writel(val, base + FTM_SC); 94} 95 96static inline void ftm_reset_counter(void __iomem *base) 97{ 98 /* 99 * The CNT register contains the FTM counter value. 100 * Reset clears the CNT register. Writing any value to COUNT 101 * updates the counter with its initial value, CNTIN. 102 */ 103 ftm_writel(0x00, base + FTM_CNT); 104} 105 106static u64 notrace ftm_read_sched_clock(void) 107{ 108 return ftm_readl(priv->clksrc_base + FTM_CNT); 109} 110 111static int ftm_set_next_event(unsigned long delta, 112 struct clock_event_device *unused) 113{ 114 /* 115 * The CNNIN and MOD are all double buffer registers, writing 116 * to the MOD register latches the value into a buffer. The MOD 117 * register is updated with the value of its write buffer with 118 * the following scenario: 119 * a, the counter source clock is disabled. 120 */ 121 ftm_counter_disable(priv->clkevt_base); 122 123 /* Force the value of CNTIN to be loaded into the FTM counter */ 124 ftm_reset_counter(priv->clkevt_base); 125 126 /* 127 * The counter increments until the value of MOD is reached, 128 * at which point the counter is reloaded with the value of CNTIN. 129 * The TOF (the overflow flag) bit is set when the FTM counter 130 * changes from MOD to CNTIN. So we should using the delta - 1. 131 */ 132 ftm_writel(delta - 1, priv->clkevt_base + FTM_MOD); 133 134 ftm_counter_enable(priv->clkevt_base); 135 136 ftm_irq_enable(priv->clkevt_base); 137 138 return 0; 139} 140 141static int ftm_set_oneshot(struct clock_event_device *evt) 142{ 143 ftm_counter_disable(priv->clkevt_base); 144 return 0; 145} 146 147static int ftm_set_periodic(struct clock_event_device *evt) 148{ 149 ftm_set_next_event(priv->periodic_cyc, evt); 150 return 0; 151} 152 153static irqreturn_t ftm_evt_interrupt(int irq, void *dev_id) 154{ 155 struct clock_event_device *evt = dev_id; 156 157 ftm_irq_acknowledge(priv->clkevt_base); 158 159 if (likely(clockevent_state_oneshot(evt))) { 160 ftm_irq_disable(priv->clkevt_base); 161 ftm_counter_disable(priv->clkevt_base); 162 } 163 164 evt->event_handler(evt); 165 166 return IRQ_HANDLED; 167} 168 169static struct clock_event_device ftm_clockevent = { 170 .name = "Freescale ftm timer", 171 .features = CLOCK_EVT_FEAT_PERIODIC | 172 CLOCK_EVT_FEAT_ONESHOT, 173 .set_state_periodic = ftm_set_periodic, 174 .set_state_oneshot = ftm_set_oneshot, 175 .set_next_event = ftm_set_next_event, 176 .rating = 300, 177}; 178 179static int __init ftm_clockevent_init(unsigned long freq, int irq) 180{ 181 int err; 182 183 ftm_writel(0x00, priv->clkevt_base + FTM_CNTIN); 184 ftm_writel(~0u, priv->clkevt_base + FTM_MOD); 185 186 ftm_reset_counter(priv->clkevt_base); 187 188 err = request_irq(irq, ftm_evt_interrupt, IRQF_TIMER | IRQF_IRQPOLL, 189 "Freescale ftm timer", &ftm_clockevent); 190 if (err) { 191 pr_err("ftm: setup irq failed: %d\n", err); 192 return err; 193 } 194 195 ftm_clockevent.cpumask = cpumask_of(0); 196 ftm_clockevent.irq = irq; 197 198 clockevents_config_and_register(&ftm_clockevent, 199 freq / (1 << priv->ps), 200 1, 0xffff); 201 202 ftm_counter_enable(priv->clkevt_base); 203 204 return 0; 205} 206 207static int __init ftm_clocksource_init(unsigned long freq) 208{ 209 int err; 210 211 ftm_writel(0x00, priv->clksrc_base + FTM_CNTIN); 212 ftm_writel(~0u, priv->clksrc_base + FTM_MOD); 213 214 ftm_reset_counter(priv->clksrc_base); 215 216 sched_clock_register(ftm_read_sched_clock, 16, freq / (1 << priv->ps)); 217 err = clocksource_mmio_init(priv->clksrc_base + FTM_CNT, "fsl-ftm", 218 freq / (1 << priv->ps), 300, 16, 219 clocksource_mmio_readl_up); 220 if (err) { 221 pr_err("ftm: init clock source mmio failed: %d\n", err); 222 return err; 223 } 224 225 ftm_counter_enable(priv->clksrc_base); 226 227 return 0; 228} 229 230static int __init __ftm_clk_init(struct device_node *np, char *cnt_name, 231 char *ftm_name) 232{ 233 struct clk *clk; 234 int err; 235 236 clk = of_clk_get_by_name(np, cnt_name); 237 if (IS_ERR(clk)) { 238 pr_err("ftm: Cannot get \"%s\": %ld\n", cnt_name, PTR_ERR(clk)); 239 return PTR_ERR(clk); 240 } 241 err = clk_prepare_enable(clk); 242 if (err) { 243 pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n", 244 cnt_name, err); 245 return err; 246 } 247 248 clk = of_clk_get_by_name(np, ftm_name); 249 if (IS_ERR(clk)) { 250 pr_err("ftm: Cannot get \"%s\": %ld\n", ftm_name, PTR_ERR(clk)); 251 return PTR_ERR(clk); 252 } 253 err = clk_prepare_enable(clk); 254 if (err) 255 pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n", 256 ftm_name, err); 257 258 return clk_get_rate(clk); 259} 260 261static unsigned long __init ftm_clk_init(struct device_node *np) 262{ 263 long freq; 264 265 freq = __ftm_clk_init(np, "ftm-evt-counter-en", "ftm-evt"); 266 if (freq <= 0) 267 return 0; 268 269 freq = __ftm_clk_init(np, "ftm-src-counter-en", "ftm-src"); 270 if (freq <= 0) 271 return 0; 272 273 return freq; 274} 275 276static int __init ftm_calc_closest_round_cyc(unsigned long freq) 277{ 278 priv->ps = 0; 279 280 /* The counter register is only using the lower 16 bits, and 281 * if the 'freq' value is to big here, then the periodic_cyc 282 * may exceed 0xFFFF. 283 */ 284 do { 285 priv->periodic_cyc = DIV_ROUND_CLOSEST(freq, 286 HZ * (1 << priv->ps++)); 287 } while (priv->periodic_cyc > 0xFFFF); 288 289 if (priv->ps > FTM_PS_MAX) { 290 pr_err("ftm: the prescaler is %lu > %d\n", 291 priv->ps, FTM_PS_MAX); 292 return -EINVAL; 293 } 294 295 return 0; 296} 297 298static int __init ftm_timer_init(struct device_node *np) 299{ 300 unsigned long freq; 301 int ret, irq; 302 303 priv = kzalloc(sizeof(*priv), GFP_KERNEL); 304 if (!priv) 305 return -ENOMEM; 306 307 ret = -ENXIO; 308 priv->clkevt_base = of_iomap(np, 0); 309 if (!priv->clkevt_base) { 310 pr_err("ftm: unable to map event timer registers\n"); 311 goto err_clkevt; 312 } 313 314 priv->clksrc_base = of_iomap(np, 1); 315 if (!priv->clksrc_base) { 316 pr_err("ftm: unable to map source timer registers\n"); 317 goto err_clksrc; 318 } 319 320 ret = -EINVAL; 321 irq = irq_of_parse_and_map(np, 0); 322 if (irq <= 0) { 323 pr_err("ftm: unable to get IRQ from DT, %d\n", irq); 324 goto err; 325 } 326 327 priv->big_endian = of_property_read_bool(np, "big-endian"); 328 329 freq = ftm_clk_init(np); 330 if (!freq) 331 goto err; 332 333 ret = ftm_calc_closest_round_cyc(freq); 334 if (ret) 335 goto err; 336 337 ret = ftm_clocksource_init(freq); 338 if (ret) 339 goto err; 340 341 ret = ftm_clockevent_init(freq, irq); 342 if (ret) 343 goto err; 344 345 return 0; 346 347err: 348 iounmap(priv->clksrc_base); 349err_clksrc: 350 iounmap(priv->clkevt_base); 351err_clkevt: 352 kfree(priv); 353 return ret; 354} 355TIMER_OF_DECLARE(flextimer, "fsl,ftm-timer", ftm_timer_init);