linux/drivers/clocksource/sun4i_timer.c
<<
>>
Prefs
   1/*
   2 * Allwinner A1X SoCs timer handling.
   3 *
   4 * Copyright (C) 2012 Maxime Ripard
   5 *
   6 * Maxime Ripard <maxime.ripard@free-electrons.com>
   7 *
   8 * Based on code from
   9 * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
  10 * Benn Huang <benn@allwinnertech.com>
  11 *
  12 * This file is licensed under the terms of the GNU General Public
  13 * License version 2.  This program is licensed "as is" without any
  14 * warranty of any kind, whether express or implied.
  15 */
  16
  17#include <linux/clk.h>
  18#include <linux/clockchips.h>
  19#include <linux/interrupt.h>
  20#include <linux/irq.h>
  21#include <linux/irqreturn.h>
  22#include <linux/sched_clock.h>
  23#include <linux/of.h>
  24#include <linux/of_address.h>
  25#include <linux/of_irq.h>
  26
  27#define TIMER_IRQ_EN_REG        0x00
  28#define TIMER_IRQ_EN(val)               BIT(val)
  29#define TIMER_IRQ_ST_REG        0x04
  30#define TIMER_CTL_REG(val)      (0x10 * val + 0x10)
  31#define TIMER_CTL_ENABLE                BIT(0)
  32#define TIMER_CTL_RELOAD                BIT(1)
  33#define TIMER_CTL_CLK_SRC(val)          (((val) & 0x3) << 2)
  34#define TIMER_CTL_CLK_SRC_OSC24M                (1)
  35#define TIMER_CTL_CLK_PRES(val)         (((val) & 0x7) << 4)
  36#define TIMER_CTL_ONESHOT               BIT(7)
  37#define TIMER_INTVAL_REG(val)   (0x10 * (val) + 0x14)
  38#define TIMER_CNTVAL_REG(val)   (0x10 * (val) + 0x18)
  39
  40#define TIMER_SYNC_TICKS        3
  41
  42static void __iomem *timer_base;
  43static u32 ticks_per_jiffy;
  44
  45/*
  46 * When we disable a timer, we need to wait at least for 2 cycles of
  47 * the timer source clock. We will use for that the clocksource timer
  48 * that is already setup and runs at the same frequency than the other
  49 * timers, and we never will be disabled.
  50 */
  51static void sun4i_clkevt_sync(void)
  52{
  53        u32 old = readl(timer_base + TIMER_CNTVAL_REG(1));
  54
  55        while ((old - readl(timer_base + TIMER_CNTVAL_REG(1))) < TIMER_SYNC_TICKS)
  56                cpu_relax();
  57}
  58
  59static void sun4i_clkevt_time_stop(u8 timer)
  60{
  61        u32 val = readl(timer_base + TIMER_CTL_REG(timer));
  62        writel(val & ~TIMER_CTL_ENABLE, timer_base + TIMER_CTL_REG(timer));
  63        sun4i_clkevt_sync();
  64}
  65
  66static void sun4i_clkevt_time_setup(u8 timer, unsigned long delay)
  67{
  68        writel(delay, timer_base + TIMER_INTVAL_REG(timer));
  69}
  70
  71static void sun4i_clkevt_time_start(u8 timer, bool periodic)
  72{
  73        u32 val = readl(timer_base + TIMER_CTL_REG(timer));
  74
  75        if (periodic)
  76                val &= ~TIMER_CTL_ONESHOT;
  77        else
  78                val |= TIMER_CTL_ONESHOT;
  79
  80        writel(val | TIMER_CTL_ENABLE | TIMER_CTL_RELOAD,
  81               timer_base + TIMER_CTL_REG(timer));
  82}
  83
  84static int sun4i_clkevt_shutdown(struct clock_event_device *evt)
  85{
  86        sun4i_clkevt_time_stop(0);
  87        return 0;
  88}
  89
  90static int sun4i_clkevt_set_oneshot(struct clock_event_device *evt)
  91{
  92        sun4i_clkevt_time_stop(0);
  93        sun4i_clkevt_time_start(0, false);
  94        return 0;
  95}
  96
  97static int sun4i_clkevt_set_periodic(struct clock_event_device *evt)
  98{
  99        sun4i_clkevt_time_stop(0);
 100        sun4i_clkevt_time_setup(0, ticks_per_jiffy);
 101        sun4i_clkevt_time_start(0, true);
 102        return 0;
 103}
 104
 105static int sun4i_clkevt_next_event(unsigned long evt,
 106                                   struct clock_event_device *unused)
 107{
 108        sun4i_clkevt_time_stop(0);
 109        sun4i_clkevt_time_setup(0, evt - TIMER_SYNC_TICKS);
 110        sun4i_clkevt_time_start(0, false);
 111
 112        return 0;
 113}
 114
 115static struct clock_event_device sun4i_clockevent = {
 116        .name = "sun4i_tick",
 117        .rating = 350,
 118        .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
 119        .set_state_shutdown = sun4i_clkevt_shutdown,
 120        .set_state_periodic = sun4i_clkevt_set_periodic,
 121        .set_state_oneshot = sun4i_clkevt_set_oneshot,
 122        .tick_resume = sun4i_clkevt_shutdown,
 123        .set_next_event = sun4i_clkevt_next_event,
 124};
 125
 126static void sun4i_timer_clear_interrupt(void)
 127{
 128        writel(TIMER_IRQ_EN(0), timer_base + TIMER_IRQ_ST_REG);
 129}
 130
 131static irqreturn_t sun4i_timer_interrupt(int irq, void *dev_id)
 132{
 133        struct clock_event_device *evt = (struct clock_event_device *)dev_id;
 134
 135        sun4i_timer_clear_interrupt();
 136        evt->event_handler(evt);
 137
 138        return IRQ_HANDLED;
 139}
 140
 141static struct irqaction sun4i_timer_irq = {
 142        .name = "sun4i_timer0",
 143        .flags = IRQF_TIMER | IRQF_IRQPOLL,
 144        .handler = sun4i_timer_interrupt,
 145        .dev_id = &sun4i_clockevent,
 146};
 147
 148static u64 notrace sun4i_timer_sched_read(void)
 149{
 150        return ~readl(timer_base + TIMER_CNTVAL_REG(1));
 151}
 152
 153static int __init sun4i_timer_init(struct device_node *node)
 154{
 155        unsigned long rate = 0;
 156        struct clk *clk;
 157        int ret, irq;
 158        u32 val;
 159
 160        timer_base = of_iomap(node, 0);
 161        if (!timer_base) {
 162                pr_crit("Can't map registers\n");
 163                return -ENXIO;
 164        }
 165
 166        irq = irq_of_parse_and_map(node, 0);
 167        if (irq <= 0) {
 168                pr_crit("Can't parse IRQ\n");
 169                return -EINVAL;
 170        }
 171
 172        clk = of_clk_get(node, 0);
 173        if (IS_ERR(clk)) {
 174                pr_crit("Can't get timer clock\n");
 175                return PTR_ERR(clk);
 176        }
 177
 178        ret = clk_prepare_enable(clk);
 179        if (ret) {
 180                pr_err("Failed to prepare clock\n");
 181                return ret;
 182        }
 183
 184        rate = clk_get_rate(clk);
 185
 186        writel(~0, timer_base + TIMER_INTVAL_REG(1));
 187        writel(TIMER_CTL_ENABLE | TIMER_CTL_RELOAD |
 188               TIMER_CTL_CLK_SRC(TIMER_CTL_CLK_SRC_OSC24M),
 189               timer_base + TIMER_CTL_REG(1));
 190
 191        /*
 192         * sched_clock_register does not have priorities, and on sun6i and
 193         * later there is a better sched_clock registered by arm_arch_timer.c
 194         */
 195        if (of_machine_is_compatible("allwinner,sun4i-a10") ||
 196            of_machine_is_compatible("allwinner,sun5i-a13") ||
 197            of_machine_is_compatible("allwinner,sun5i-a10s"))
 198                sched_clock_register(sun4i_timer_sched_read, 32, rate);
 199
 200        ret = clocksource_mmio_init(timer_base + TIMER_CNTVAL_REG(1), node->name,
 201                                    rate, 350, 32, clocksource_mmio_readl_down);
 202        if (ret) {
 203                pr_err("Failed to register clocksource\n");
 204                return ret;
 205        }
 206
 207        ticks_per_jiffy = DIV_ROUND_UP(rate, HZ);
 208
 209        writel(TIMER_CTL_CLK_SRC(TIMER_CTL_CLK_SRC_OSC24M),
 210               timer_base + TIMER_CTL_REG(0));
 211
 212        /* Make sure timer is stopped before playing with interrupts */
 213        sun4i_clkevt_time_stop(0);
 214
 215        /* clear timer0 interrupt */
 216        sun4i_timer_clear_interrupt();
 217
 218        sun4i_clockevent.cpumask = cpu_possible_mask;
 219        sun4i_clockevent.irq = irq;
 220
 221        clockevents_config_and_register(&sun4i_clockevent, rate,
 222                                        TIMER_SYNC_TICKS, 0xffffffff);
 223
 224        ret = setup_irq(irq, &sun4i_timer_irq);
 225        if (ret) {
 226                pr_err("failed to setup irq %d\n", irq);
 227                return ret;
 228        }
 229
 230        /* Enable timer0 interrupt */
 231        val = readl(timer_base + TIMER_IRQ_EN_REG);
 232        writel(val | TIMER_IRQ_EN(0), timer_base + TIMER_IRQ_EN_REG);
 233
 234        return ret;
 235}
 236CLOCKSOURCE_OF_DECLARE(sun4i, "allwinner,sun4i-a10-timer",
 237                       sun4i_timer_init);
 238