linux/drivers/clocksource/timer-integrator-ap.c
<<
>>
Prefs
   1/*
   2 * Integrator/AP timer driver
   3 * Copyright (C) 2000-2003 Deep Blue Solutions Ltd
   4 * Copyright (c) 2014, Linaro Limited
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License as published by
   8 * the Free Software Foundation; either version 2 of the License, or
   9 * (at your option) any later version.
  10 *
  11 * This program is distributed in the hope that it will be useful,
  12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14 * GNU General Public License for more details.
  15 *
  16 * You should have received a copy of the GNU General Public License
  17 * along with this program; if not, write to the Free Software
  18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  19 */
  20
  21#include <linux/clk.h>
  22#include <linux/clocksource.h>
  23#include <linux/of_irq.h>
  24#include <linux/of_address.h>
  25#include <linux/of_platform.h>
  26#include <linux/clockchips.h>
  27#include <linux/interrupt.h>
  28#include <linux/sched_clock.h>
  29
  30#include "timer-sp.h"
  31
  32static void __iomem * sched_clk_base;
  33
  34static u64 notrace integrator_read_sched_clock(void)
  35{
  36        return -readl(sched_clk_base + TIMER_VALUE);
  37}
  38
  39static int integrator_clocksource_init(unsigned long inrate,
  40                                       void __iomem *base)
  41{
  42        u32 ctrl = TIMER_CTRL_ENABLE | TIMER_CTRL_PERIODIC;
  43        unsigned long rate = inrate;
  44        int ret;
  45
  46        if (rate >= 1500000) {
  47                rate /= 16;
  48                ctrl |= TIMER_CTRL_DIV16;
  49        }
  50
  51        writel(0xffff, base + TIMER_LOAD);
  52        writel(ctrl, base + TIMER_CTRL);
  53
  54        ret = clocksource_mmio_init(base + TIMER_VALUE, "timer2",
  55                                    rate, 200, 16, clocksource_mmio_readl_down);
  56        if (ret)
  57                return ret;
  58
  59        sched_clk_base = base;
  60        sched_clock_register(integrator_read_sched_clock, 16, rate);
  61
  62        return 0;
  63}
  64
  65static unsigned long timer_reload;
  66static void __iomem * clkevt_base;
  67
  68/*
  69 * IRQ handler for the timer
  70 */
  71static irqreturn_t integrator_timer_interrupt(int irq, void *dev_id)
  72{
  73        struct clock_event_device *evt = dev_id;
  74
  75        /* clear the interrupt */
  76        writel(1, clkevt_base + TIMER_INTCLR);
  77
  78        evt->event_handler(evt);
  79
  80        return IRQ_HANDLED;
  81}
  82
  83static int clkevt_shutdown(struct clock_event_device *evt)
  84{
  85        u32 ctrl = readl(clkevt_base + TIMER_CTRL) & ~TIMER_CTRL_ENABLE;
  86
  87        /* Disable timer */
  88        writel(ctrl, clkevt_base + TIMER_CTRL);
  89        return 0;
  90}
  91
  92static int clkevt_set_oneshot(struct clock_event_device *evt)
  93{
  94        u32 ctrl = readl(clkevt_base + TIMER_CTRL) &
  95                   ~(TIMER_CTRL_ENABLE | TIMER_CTRL_PERIODIC);
  96
  97        /* Leave the timer disabled, .set_next_event will enable it */
  98        writel(ctrl, clkevt_base + TIMER_CTRL);
  99        return 0;
 100}
 101
 102static int clkevt_set_periodic(struct clock_event_device *evt)
 103{
 104        u32 ctrl = readl(clkevt_base + TIMER_CTRL) & ~TIMER_CTRL_ENABLE;
 105
 106        /* Disable timer */
 107        writel(ctrl, clkevt_base + TIMER_CTRL);
 108
 109        /* Enable the timer and start the periodic tick */
 110        writel(timer_reload, clkevt_base + TIMER_LOAD);
 111        ctrl |= TIMER_CTRL_PERIODIC | TIMER_CTRL_ENABLE;
 112        writel(ctrl, clkevt_base + TIMER_CTRL);
 113        return 0;
 114}
 115
 116static int clkevt_set_next_event(unsigned long next, struct clock_event_device *evt)
 117{
 118        unsigned long ctrl = readl(clkevt_base + TIMER_CTRL);
 119
 120        writel(ctrl & ~TIMER_CTRL_ENABLE, clkevt_base + TIMER_CTRL);
 121        writel(next, clkevt_base + TIMER_LOAD);
 122        writel(ctrl | TIMER_CTRL_ENABLE, clkevt_base + TIMER_CTRL);
 123
 124        return 0;
 125}
 126
 127static struct clock_event_device integrator_clockevent = {
 128        .name                   = "timer1",
 129        .features               = CLOCK_EVT_FEAT_PERIODIC |
 130                                  CLOCK_EVT_FEAT_ONESHOT,
 131        .set_state_shutdown     = clkevt_shutdown,
 132        .set_state_periodic     = clkevt_set_periodic,
 133        .set_state_oneshot      = clkevt_set_oneshot,
 134        .tick_resume            = clkevt_shutdown,
 135        .set_next_event         = clkevt_set_next_event,
 136        .rating                 = 300,
 137};
 138
 139static struct irqaction integrator_timer_irq = {
 140        .name           = "timer",
 141        .flags          = IRQF_TIMER | IRQF_IRQPOLL,
 142        .handler        = integrator_timer_interrupt,
 143        .dev_id         = &integrator_clockevent,
 144};
 145
 146static int integrator_clockevent_init(unsigned long inrate,
 147                                      void __iomem *base, int irq)
 148{
 149        unsigned long rate = inrate;
 150        unsigned int ctrl = 0;
 151        int ret;
 152
 153        clkevt_base = base;
 154        /* Calculate and program a divisor */
 155        if (rate > 0x100000 * HZ) {
 156                rate /= 256;
 157                ctrl |= TIMER_CTRL_DIV256;
 158        } else if (rate > 0x10000 * HZ) {
 159                rate /= 16;
 160                ctrl |= TIMER_CTRL_DIV16;
 161        }
 162        timer_reload = rate / HZ;
 163        writel(ctrl, clkevt_base + TIMER_CTRL);
 164
 165        ret = setup_irq(irq, &integrator_timer_irq);
 166        if (ret)
 167                return ret;
 168
 169        clockevents_config_and_register(&integrator_clockevent,
 170                                        rate,
 171                                        1,
 172                                        0xffffU);
 173        return 0;
 174}
 175
 176static int __init integrator_ap_timer_init_of(struct device_node *node)
 177{
 178        const char *path;
 179        void __iomem *base;
 180        int err;
 181        int irq;
 182        struct clk *clk;
 183        unsigned long rate;
 184        struct device_node *pri_node;
 185        struct device_node *sec_node;
 186
 187        base = of_io_request_and_map(node, 0, "integrator-timer");
 188        if (IS_ERR(base))
 189                return PTR_ERR(base);
 190
 191        clk = of_clk_get(node, 0);
 192        if (IS_ERR(clk)) {
 193                pr_err("No clock for %s\n", node->name);
 194                return PTR_ERR(clk);
 195        }
 196        clk_prepare_enable(clk);
 197        rate = clk_get_rate(clk);
 198        writel(0, base + TIMER_CTRL);
 199
 200        err = of_property_read_string(of_aliases,
 201                                "arm,timer-primary", &path);
 202        if (err) {
 203                pr_warn("Failed to read property");
 204                return err;
 205        }
 206
 207        pri_node = of_find_node_by_path(path);
 208
 209        err = of_property_read_string(of_aliases,
 210                                "arm,timer-secondary", &path);
 211        if (err) {
 212                pr_warn("Failed to read property");             
 213                return err;
 214        }
 215
 216
 217        sec_node = of_find_node_by_path(path);
 218
 219        if (node == pri_node)
 220                /* The primary timer lacks IRQ, use as clocksource */
 221                return integrator_clocksource_init(rate, base);
 222
 223        if (node == sec_node) {
 224                /* The secondary timer will drive the clock event */
 225                irq = irq_of_parse_and_map(node, 0);
 226                return integrator_clockevent_init(rate, base, irq);
 227        }
 228
 229        pr_info("Timer @%p unused\n", base);
 230        clk_disable_unprepare(clk);
 231
 232        return 0;
 233}
 234
 235CLOCKSOURCE_OF_DECLARE(integrator_ap_timer, "arm,integrator-timer",
 236                       integrator_ap_timer_init_of);
 237