linux/drivers/clocksource/timer-integrator-ap.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Integrator/AP timer driver
   4 * Copyright (C) 2000-2003 Deep Blue Solutions Ltd
   5 * Copyright (c) 2014, Linaro Limited
   6 */
   7
   8#include <linux/clk.h>
   9#include <linux/clocksource.h>
  10#include <linux/of_irq.h>
  11#include <linux/of_address.h>
  12#include <linux/of_platform.h>
  13#include <linux/clockchips.h>
  14#include <linux/interrupt.h>
  15#include <linux/sched_clock.h>
  16
  17#include "timer-sp.h"
  18
  19static void __iomem * sched_clk_base;
  20
  21static u64 notrace integrator_read_sched_clock(void)
  22{
  23        return -readl(sched_clk_base + TIMER_VALUE);
  24}
  25
  26static int __init integrator_clocksource_init(unsigned long inrate,
  27                                              void __iomem *base)
  28{
  29        u32 ctrl = TIMER_CTRL_ENABLE | TIMER_CTRL_PERIODIC;
  30        unsigned long rate = inrate;
  31        int ret;
  32
  33        if (rate >= 1500000) {
  34                rate /= 16;
  35                ctrl |= TIMER_CTRL_DIV16;
  36        }
  37
  38        writel(0xffff, base + TIMER_LOAD);
  39        writel(ctrl, base + TIMER_CTRL);
  40
  41        ret = clocksource_mmio_init(base + TIMER_VALUE, "timer2",
  42                                    rate, 200, 16, clocksource_mmio_readl_down);
  43        if (ret)
  44                return ret;
  45
  46        sched_clk_base = base;
  47        sched_clock_register(integrator_read_sched_clock, 16, rate);
  48
  49        return 0;
  50}
  51
  52static unsigned long timer_reload;
  53static void __iomem * clkevt_base;
  54
  55/*
  56 * IRQ handler for the timer
  57 */
  58static irqreturn_t integrator_timer_interrupt(int irq, void *dev_id)
  59{
  60        struct clock_event_device *evt = dev_id;
  61
  62        /* clear the interrupt */
  63        writel(1, clkevt_base + TIMER_INTCLR);
  64
  65        evt->event_handler(evt);
  66
  67        return IRQ_HANDLED;
  68}
  69
  70static int clkevt_shutdown(struct clock_event_device *evt)
  71{
  72        u32 ctrl = readl(clkevt_base + TIMER_CTRL) & ~TIMER_CTRL_ENABLE;
  73
  74        /* Disable timer */
  75        writel(ctrl, clkevt_base + TIMER_CTRL);
  76        return 0;
  77}
  78
  79static int clkevt_set_oneshot(struct clock_event_device *evt)
  80{
  81        u32 ctrl = readl(clkevt_base + TIMER_CTRL) &
  82                   ~(TIMER_CTRL_ENABLE | TIMER_CTRL_PERIODIC);
  83
  84        /* Leave the timer disabled, .set_next_event will enable it */
  85        writel(ctrl, clkevt_base + TIMER_CTRL);
  86        return 0;
  87}
  88
  89static int clkevt_set_periodic(struct clock_event_device *evt)
  90{
  91        u32 ctrl = readl(clkevt_base + TIMER_CTRL) & ~TIMER_CTRL_ENABLE;
  92
  93        /* Disable timer */
  94        writel(ctrl, clkevt_base + TIMER_CTRL);
  95
  96        /* Enable the timer and start the periodic tick */
  97        writel(timer_reload, clkevt_base + TIMER_LOAD);
  98        ctrl |= TIMER_CTRL_PERIODIC | TIMER_CTRL_ENABLE;
  99        writel(ctrl, clkevt_base + TIMER_CTRL);
 100        return 0;
 101}
 102
 103static int clkevt_set_next_event(unsigned long next, struct clock_event_device *evt)
 104{
 105        unsigned long ctrl = readl(clkevt_base + TIMER_CTRL);
 106
 107        writel(ctrl & ~TIMER_CTRL_ENABLE, clkevt_base + TIMER_CTRL);
 108        writel(next, clkevt_base + TIMER_LOAD);
 109        writel(ctrl | TIMER_CTRL_ENABLE, clkevt_base + TIMER_CTRL);
 110
 111        return 0;
 112}
 113
 114static struct clock_event_device integrator_clockevent = {
 115        .name                   = "timer1",
 116        .features               = CLOCK_EVT_FEAT_PERIODIC |
 117                                  CLOCK_EVT_FEAT_ONESHOT,
 118        .set_state_shutdown     = clkevt_shutdown,
 119        .set_state_periodic     = clkevt_set_periodic,
 120        .set_state_oneshot      = clkevt_set_oneshot,
 121        .tick_resume            = clkevt_shutdown,
 122        .set_next_event         = clkevt_set_next_event,
 123        .rating                 = 300,
 124};
 125
 126static int integrator_clockevent_init(unsigned long inrate,
 127                                      void __iomem *base, int irq)
 128{
 129        unsigned long rate = inrate;
 130        unsigned int ctrl = 0;
 131        int ret;
 132
 133        clkevt_base = base;
 134        /* Calculate and program a divisor */
 135        if (rate > 0x100000 * HZ) {
 136                rate /= 256;
 137                ctrl |= TIMER_CTRL_DIV256;
 138        } else if (rate > 0x10000 * HZ) {
 139                rate /= 16;
 140                ctrl |= TIMER_CTRL_DIV16;
 141        }
 142        timer_reload = rate / HZ;
 143        writel(ctrl, clkevt_base + TIMER_CTRL);
 144
 145        ret = request_irq(irq, integrator_timer_interrupt,
 146                          IRQF_TIMER | IRQF_IRQPOLL, "timer",
 147                          &integrator_clockevent);
 148        if (ret)
 149                return ret;
 150
 151        clockevents_config_and_register(&integrator_clockevent,
 152                                        rate,
 153                                        1,
 154                                        0xffffU);
 155        return 0;
 156}
 157
 158static int __init integrator_ap_timer_init_of(struct device_node *node)
 159{
 160        const char *path;
 161        void __iomem *base;
 162        int err;
 163        int irq;
 164        struct clk *clk;
 165        unsigned long rate;
 166        struct device_node *alias_node;
 167
 168        base = of_io_request_and_map(node, 0, "integrator-timer");
 169        if (IS_ERR(base))
 170                return PTR_ERR(base);
 171
 172        clk = of_clk_get(node, 0);
 173        if (IS_ERR(clk)) {
 174                pr_err("No clock for %pOFn\n", node);
 175                return PTR_ERR(clk);
 176        }
 177        clk_prepare_enable(clk);
 178        rate = clk_get_rate(clk);
 179        writel(0, base + TIMER_CTRL);
 180
 181        err = of_property_read_string(of_aliases,
 182                                "arm,timer-primary", &path);
 183        if (err) {
 184                pr_warn("Failed to read property\n");
 185                return err;
 186        }
 187
 188        alias_node = of_find_node_by_path(path);
 189
 190        /*
 191         * The pointer is used as an identifier not as a pointer, we
 192         * can drop the refcount on the of__node immediately after
 193         * getting it.
 194         */
 195        of_node_put(alias_node);
 196
 197        if (node == alias_node)
 198                /* The primary timer lacks IRQ, use as clocksource */
 199                return integrator_clocksource_init(rate, base);
 200
 201        err = of_property_read_string(of_aliases,
 202                                "arm,timer-secondary", &path);
 203        if (err) {
 204                pr_warn("Failed to read property\n");
 205                return err;
 206        }
 207
 208        alias_node = of_find_node_by_path(path);
 209
 210        of_node_put(alias_node);
 211
 212        if (node == alias_node) {
 213                /* The secondary timer will drive the clock event */
 214                irq = irq_of_parse_and_map(node, 0);
 215                return integrator_clockevent_init(rate, base, irq);
 216        }
 217
 218        pr_info("Timer @%p unused\n", base);
 219        clk_disable_unprepare(clk);
 220
 221        return 0;
 222}
 223
 224TIMER_OF_DECLARE(integrator_ap_timer, "arm,integrator-timer",
 225                       integrator_ap_timer_init_of);
 226