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