356 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*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 else37 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 else45 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 COUNT101 * 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, writing116 * to the MOD register latches the value into a buffer. The MOD117 * register is updated with the value of its write buffer with118 * 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 counter130 * 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, and281 * if the 'freq' value is to big here, then the periodic_cyc282 * 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);356