]> Pileus Git - ~andy/linux/blobdiff - drivers/clocksource/sun4i_timer.c
Merge tag 'ext4_for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso...
[~andy/linux] / drivers / clocksource / sun4i_timer.c
index 34ab6580ad1cb17f53ab906281a268e16470f8cf..8ead0258740a82dc71807bd323f5b1eb9d1535a5 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/irqreturn.h>
+#include <linux/sched_clock.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/of_irq.h>
 #define TIMER_IRQ_ST_REG       0x04
 #define TIMER_CTL_REG(val)     (0x10 * val + 0x10)
 #define TIMER_CTL_ENABLE               BIT(0)
-#define TIMER_CTL_AUTORELOAD           BIT(1)
+#define TIMER_CTL_RELOAD               BIT(1)
+#define TIMER_CTL_CLK_SRC(val)         (((val) & 0x3) << 2)
+#define TIMER_CTL_CLK_SRC_OSC24M               (1)
+#define TIMER_CTL_CLK_PRES(val)                (((val) & 0x7) << 4)
 #define TIMER_CTL_ONESHOT              BIT(7)
 #define TIMER_INTVAL_REG(val)  (0x10 * (val) + 0x14)
 #define TIMER_CNTVAL_REG(val)  (0x10 * (val) + 0x18)
 
-#define TIMER_SCAL             16
-
 static void __iomem *timer_base;
+static u32 ticks_per_jiffy;
+
+/*
+ * When we disable a timer, we need to wait at least for 2 cycles of
+ * the timer source clock. We will use for that the clocksource timer
+ * that is already setup and runs at the same frequency than the other
+ * timers, and we never will be disabled.
+ */
+static void sun4i_clkevt_sync(void)
+{
+       u32 old = readl(timer_base + TIMER_CNTVAL_REG(1));
+
+       while ((old - readl(timer_base + TIMER_CNTVAL_REG(1))) < 3)
+               cpu_relax();
+}
+
+static void sun4i_clkevt_time_stop(u8 timer)
+{
+       u32 val = readl(timer_base + TIMER_CTL_REG(timer));
+       writel(val & ~TIMER_CTL_ENABLE, timer_base + TIMER_CTL_REG(timer));
+       sun4i_clkevt_sync();
+}
+
+static void sun4i_clkevt_time_setup(u8 timer, unsigned long delay)
+{
+       writel(delay, timer_base + TIMER_INTVAL_REG(timer));
+}
+
+static void sun4i_clkevt_time_start(u8 timer, bool periodic)
+{
+       u32 val = readl(timer_base + TIMER_CTL_REG(timer));
+
+       if (periodic)
+               val &= ~TIMER_CTL_ONESHOT;
+       else
+               val |= TIMER_CTL_ONESHOT;
+
+       writel(val | TIMER_CTL_ENABLE | TIMER_CTL_RELOAD,
+              timer_base + TIMER_CTL_REG(timer));
+}
 
 static void sun4i_clkevt_mode(enum clock_event_mode mode,
                              struct clock_event_device *clk)
 {
-       u32 u = readl(timer_base + TIMER_CTL_REG(0));
-
        switch (mode) {
        case CLOCK_EVT_MODE_PERIODIC:
-               u &= ~(TIMER_CTL_ONESHOT);
-               writel(u | TIMER_CTL_ENABLE, timer_base + TIMER_CTL_REG(0));
+               sun4i_clkevt_time_stop(0);
+               sun4i_clkevt_time_setup(0, ticks_per_jiffy);
+               sun4i_clkevt_time_start(0, true);
                break;
-
        case CLOCK_EVT_MODE_ONESHOT:
-               writel(u | TIMER_CTL_ONESHOT, timer_base + TIMER_CTL_REG(0));
+               sun4i_clkevt_time_stop(0);
+               sun4i_clkevt_time_start(0, false);
                break;
        case CLOCK_EVT_MODE_UNUSED:
        case CLOCK_EVT_MODE_SHUTDOWN:
        default:
-               writel(u & ~(TIMER_CTL_ENABLE), timer_base + TIMER_CTL_REG(0));
+               sun4i_clkevt_time_stop(0);
                break;
        }
 }
@@ -62,10 +103,9 @@ static void sun4i_clkevt_mode(enum clock_event_mode mode,
 static int sun4i_clkevt_next_event(unsigned long evt,
                                   struct clock_event_device *unused)
 {
-       u32 u = readl(timer_base + TIMER_CTL_REG(0));
-       writel(evt, timer_base + TIMER_CNTVAL_REG(0));
-       writel(u | TIMER_CTL_ENABLE | TIMER_CTL_AUTORELOAD,
-              timer_base + TIMER_CTL_REG(0));
+       sun4i_clkevt_time_stop(0);
+       sun4i_clkevt_time_setup(0, evt);
+       sun4i_clkevt_time_start(0, false);
 
        return 0;
 }
@@ -96,6 +136,11 @@ static struct irqaction sun4i_timer_irq = {
        .dev_id = &sun4i_clockevent,
 };
 
+static u32 sun4i_timer_sched_read(void)
+{
+       return ~readl(timer_base + TIMER_CNTVAL_REG(1));
+}
+
 static void __init sun4i_timer_init(struct device_node *node)
 {
        unsigned long rate = 0;
@@ -114,22 +159,23 @@ static void __init sun4i_timer_init(struct device_node *node)
        clk = of_clk_get(node, 0);
        if (IS_ERR(clk))
                panic("Can't get timer clock");
+       clk_prepare_enable(clk);
 
        rate = clk_get_rate(clk);
 
-       writel(rate / (TIMER_SCAL * HZ),
-              timer_base + TIMER_INTVAL_REG(0));
+       writel(~0, timer_base + TIMER_INTVAL_REG(1));
+       writel(TIMER_CTL_ENABLE | TIMER_CTL_RELOAD |
+              TIMER_CTL_CLK_SRC(TIMER_CTL_CLK_SRC_OSC24M),
+              timer_base + TIMER_CTL_REG(1));
+
+       setup_sched_clock(sun4i_timer_sched_read, 32, rate);
+       clocksource_mmio_init(timer_base + TIMER_CNTVAL_REG(1), node->name,
+                             rate, 300, 32, clocksource_mmio_readl_down);
 
-       /* set clock source to HOSC, 16 pre-division */
-       val = readl(timer_base + TIMER_CTL_REG(0));
-       val &= ~(0x07 << 4);
-       val &= ~(0x03 << 2);
-       val |= (4 << 4) | (1 << 2);
-       writel(val, timer_base + TIMER_CTL_REG(0));
+       ticks_per_jiffy = DIV_ROUND_UP(rate, HZ);
 
-       /* set mode to auto reload */
-       val = readl(timer_base + TIMER_CTL_REG(0));
-       writel(val | TIMER_CTL_AUTORELOAD, timer_base + TIMER_CTL_REG(0));
+       writel(TIMER_CTL_CLK_SRC(TIMER_CTL_CLK_SRC_OSC24M),
+              timer_base + TIMER_CTL_REG(0));
 
        ret = setup_irq(irq, &sun4i_timer_irq);
        if (ret)
@@ -141,8 +187,8 @@ static void __init sun4i_timer_init(struct device_node *node)
 
        sun4i_clockevent.cpumask = cpumask_of(0);
 
-       clockevents_config_and_register(&sun4i_clockevent, rate / TIMER_SCAL,
-                                       0x1, 0xff);
+       clockevents_config_and_register(&sun4i_clockevent, rate, 0x1,
+                                       0xffffffff);
 }
 CLOCKSOURCE_OF_DECLARE(sun4i, "allwinner,sun4i-timer",
                       sun4i_timer_init);