diff options
Diffstat (limited to 'drivers/clocksource/sun4i_timer.c')
-rw-r--r-- | drivers/clocksource/sun4i_timer.c | 110 |
1 files changed, 78 insertions, 32 deletions
diff --git a/drivers/clocksource/sun4i_timer.c b/drivers/clocksource/sun4i_timer.c index d4674e78ef3..8ead0258740 100644 --- a/drivers/clocksource/sun4i_timer.c +++ b/drivers/clocksource/sun4i_timer.c @@ -19,42 +19,83 @@ #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_EN_REG 0x00 -#define TIMER_IRQ_EN(val) (1 << val) +#define TIMER_IRQ_EN(val) BIT(val) #define TIMER_IRQ_ST_REG 0x04 #define TIMER_CTL_REG(val) (0x10 * val + 0x10) -#define TIMER_CTL_ENABLE (1 << 0) -#define TIMER_CTL_AUTORELOAD (1 << 1) -#define TIMER_CTL_ONESHOT (1 << 7) -#define TIMER_INTVAL_REG(val) (0x10 * val + 0x14) -#define TIMER_CNTVAL_REG(val) (0x10 * val + 0x18) - -#define TIMER_SCAL 16 +#define TIMER_CTL_ENABLE BIT(0) +#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) 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); |